summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/intl402
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/test262/intl402')
-rw-r--r--js/src/tests/test262/intl402/Array/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/toLocaleString/calls-toLocaleString-number-elements.js19
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/toLocaleString/invoke-element-tolocalestring.js63
-rw-r--r--js/src/tests/test262/intl402/Array/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Array/shell.js0
-rw-r--r--js/src/tests/test262/intl402/BigInt/browser.js0
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/builtin.js31
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/de-DE.js25
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/default-options-object-prototype.js22
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/en-US.js25
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/length.js36
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/name.js33
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/prop-desc.js32
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/returns-same-results-as-NumberFormat.js49
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/shell.js24
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/taint-Intl-NumberFormat.js17
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/this-value-invalid.js31
-rw-r--r--js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js50
-rw-r--r--js/src/tests/test262/intl402/BigInt/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/builtin.js21
-rw-r--r--js/src/tests/test262/intl402/Collator/constructor-options-throwing-getters.js31
-rw-r--r--js/src/tests/test262/intl402/Collator/default-options-object-prototype.js22
-rw-r--r--js/src/tests/test262/intl402/Collator/ignore-invalid-unicode-ext-values.js48
-rw-r--r--js/src/tests/test262/intl402/Collator/instance-proto-and-extensible.js19
-rw-r--r--js/src/tests/test262/intl402/Collator/legacy-regexp-statics-not-modified.js17
-rw-r--r--js/src/tests/test262/intl402/Collator/length.js34
-rw-r--r--js/src/tests/test262/intl402/Collator/missing-unicode-ext-value-defaults-to-true.js27
-rw-r--r--js/src/tests/test262/intl402/Collator/name.js29
-rw-r--r--js/src/tests/test262/intl402/Collator/numeric-and-caseFirst.js58
-rw-r--r--js/src/tests/test262/intl402/Collator/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/proto-from-ctor-realm.js60
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/builtin.js18
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/bound-to-collator-instance.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/builtin.js32
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/canonically-equivalent-strings.js69
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-builtin.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-length.js31
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-name.js28
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-property-order.js18
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/ignorePunctuation.js19
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/length.js36
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/name.js31
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-basic.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-phonebook.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-sensitivity.js36
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/prop-desc.js39
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/compare/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/constructor/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/constructor/value.js13
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/basic.js50
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/ignorePunctuation-not-default.js17
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/length.js34
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/name.js29
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/order.js34
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/this-value-collator-prototype.js16
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/this-value-not-collator.js28
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-changed-tag.js30
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-removed-tag.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString.js26
-rw-r--r--js/src/tests/test262/intl402/Collator/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/Collator/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/subclassing.js30
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/basic.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/length.js34
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/name.js29
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Collator/supportedLocalesOf/taint-Object-prototype.js16
-rw-r--r--js/src/tests/test262/intl402/Collator/taint-Object-prototype.js18
-rw-r--r--js/src/tests/test262/intl402/Collator/test-option-ignorePunctuation.js14
-rw-r--r--js/src/tests/test262/intl402/Collator/test-option-localeMatcher.js13
-rw-r--r--js/src/tests/test262/intl402/Collator/test-option-numeric-and-caseFirst.js16
-rw-r--r--js/src/tests/test262/intl402/Collator/test-option-sensitivity.js14
-rw-r--r--js/src/tests/test262/intl402/Collator/test-option-usage.js13
-rw-r--r--js/src/tests/test262/intl402/Collator/this-value-ignored.js32
-rw-r--r--js/src/tests/test262/intl402/Collator/unicode-ext-seq-in-private-tag.js29
-rw-r--r--js/src/tests/test262/intl402/Collator/unicode-ext-seq-with-attribute.js33
-rw-r--r--js/src/tests/test262/intl402/Collator/unicode-ext-value-collation.js40
-rw-r--r--js/src/tests/test262/intl402/Collator/usage-de.js18
-rw-r--r--js/src/tests/test262/intl402/Date/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/returns-same-results-as-DateTimeFormat.js57
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/taint-Intl-DateTimeFormat.js18
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/this-value-invalid-date.js27
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/this-value-non-date.js28
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/throws-same-exceptions-as-DateTimeFormat.js56
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/length.js34
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleString/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleString/default-options-object-prototype.js21
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleString/length.js34
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleString/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/length.js34
-rw-r--r--js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Date/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/builtin.js21
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/casing-numbering-system-calendar-options.js46
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-calendar-numberingSystem-order.js51
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-default-value.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-invalid-offset-timezone.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-no-instanceof.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-invalid.js42
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-invalid.js31
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-valid.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-invalid.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-valid.js36
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-invalid.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-valid.js44
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-invalid-explicit-components.js51
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-numberingSystem-invalid.js41
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-dayPeriod.js52
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-fractionalSecondDigits.js68
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-timedate-style.js127
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order.js111
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-style-conflict.js47
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-dayPeriod.js26
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-fractionalSecondDigits.js26
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-timedate-style.js31
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-invalid.js31
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-valid.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-invalid.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/constructor-options-toobject.js40
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/date-time-options.js106
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/default-options-object-prototype.js22
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/ignore-invalid-unicode-ext-values.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/instance-proto-and-extensible.js19
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol-on-unwrap.js34
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol.js20
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/legacy-regexp-statics-not-modified.js21
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/length.js34
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/name.js29
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/numbering-system-calendar-options.js69
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/proto-from-ctor-realm.js60
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/builtin.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/value.js14
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/bound-to-datetimeformat-instance.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/builtin.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/date-constructor-not-called.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-long-en.js95
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-narrow-en.js95
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-short-en.js95
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-builtin.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-length.js31
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-name.js28
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-property-order.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/fractionalSecondDigits.js34
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/length.js36
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/name.js31
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/no-instanceof.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js29
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/proleptic-gregorian-calendar.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/prop-desc.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/related-year-zh.js20
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/shell.js55
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/taint-Object-prototype.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js52
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-zoneddatetime-not-supported.js20
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/throws-value-non-finite.js20
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-near-time-boundaries.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-to-integer.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js114
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-date-string.js41
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-near-time-boundaries.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-to-integer.js41
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-tonumber-throws.js56
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/builtin.js32
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-infinity-throws.js72
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-nan-throws.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-same-returns-single-date.js63
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-undefined-throws.js44
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-x-greater-than-y-not-throws.js35
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/en-US.js45
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/fractionalSecondDigits.js42
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/length.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/name.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/prop-desc.js23
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/shell.js55
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js58
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-zoneddatetime-not-supported.js21
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-bad-object.js28
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-is-not-object-throws.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-date-string.js41
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-near-time-boundaries.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-to-integer.js56
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-tonumber-throws.js56
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/builtin.js32
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-infinity-throws.js71
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-nan-throws.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-same-returns-single-date.js78
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-undefined-throws.js43
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-x-greater-than-y-not-throws.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/en-US.js208
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/fractionalSecondDigits.js160
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/length.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/name.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/pattern-on-calendar.js40
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/prop-desc.js23
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/shell.js384
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js117
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-zoneddatetime-not-supported.js21
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-bad-object.js28
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-is-not-object-throws.js49
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-constructor-not-called.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-infinity-throws.js35
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-nan-throws.js35
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js108
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js108
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js108
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/formatToParts.js21
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js69
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/length.js15
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/main.js76
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/name.js15
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/pattern-on-calendar.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js35
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year.js23
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/return-abrupt-tonumber-date.js44
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/shell.js360
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-resolved-time-zone.js107
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-zoneddatetime-not-supported.js20
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-has-not-internal-throws.js19
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-is-not-object-throws.js40
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-near-time-boundaries.js39
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-to-integer.js43
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/prop-desc.js19
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/basic.js53
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-dateStyle.js93
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js47
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-timeStyle.js89
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle.js102
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/length.js34
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/name.js29
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/no-instanceof.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js35
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-dayPeriod.js38
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-fractionalSecondDigits.js36
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-style.js42
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js50
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/resolved-locale-with-hc-unicode.js88
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/shell.js24
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-datetimeformat-prototype.js17
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-not-datetimeformat.js28
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-changed-tag.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-removed-tag.js24
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString.js26
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/required-date-time-formats.js48
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/subclassing.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/basic.js25
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/length.js34
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/name.js29
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/shell.js24
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/taint-Object-prototype.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-date-time-components.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-dayPeriod.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-fractionalSecondDigits.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype.js18
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/test-option-date-time-components.js17
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/test-option-formatMatcher.js13
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/test-option-hour12.js16
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/test-option-localeMatcher.js13
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/this-value-ignored.js37
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/timezone-case-insensitive.js638
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/timezone-invalid.js28
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/timezone-not-canonicalized.js30
-rw-r--r--js/src/tests/test262/intl402/DateTimeFormat/timezone-utc.js21
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/ctor-custom-get-prototype-poison-throws.js46
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/ctor-custom-prototype.js42
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/ctor-default-prototype.js26
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/instance-extensible.js40
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/length.js33
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/locales-invalid-throws.js34
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/locales-length-poison-throws.js42
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/locales-length-tolength-throws.js75
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/locales-symbol-length.js64
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/name.js29
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-fallback-abrupt-throws.js40
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-fallback-invalid-throws.js59
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-fallback-toString-abrupt-throws.js59
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-fallback-valid.js50
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-getoptionsobject.js26
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-abrupt-throws.js39
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-invalid-throws.js59
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-toString-abrupt-throws.js57
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-valid.js49
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-abrupt-throws.js39
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-invalid-throws.js69
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-toString-abrupt-throws.js69
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-valid.js50
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-null-throws.js28
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-random-properties-unchecked.js54
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-style-abrupt-throws.js46
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-style-invalid-throws.js77
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-style-toString-abrupt-throws.js72
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-style-valid.js51
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-type-abrupt-throws.js46
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-type-invalid-throws.js78
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-type-toString-abrupt-throws.js76
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/options-type-valid.js45
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prop-desc.js26
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/proto-from-ctor-realm.js41
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/proto.js15
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/Symbol.toStringTag.js23
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-invalid.js63
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-valid.js29
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-invalid.js39
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-valid.js24
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-invalid.js90
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-valid.js76
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/of/type-region-invalid.js85
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/prop-desc.js22
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/default-option-values.js82
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/length.js33
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/name.js29
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-fallback.js87
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-languagedisplay.js70
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-style.js89
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-type.js86
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/return-object.js100
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-not-object-throws.js49
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-object-lacks-internal-throws.js43
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DisplayNames/undefined-newtarget-throws.js28
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-locales-invalid.js20
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-locales-valid.js37
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-defaults.js36
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-invalid.js26
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-valid.js28
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-invalid.js19
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-invalid.js33
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-valid.js18
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-invalid.js39
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-valid.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-order.js44
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-style-conflict.js46
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-style-invalid.js40
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/constructor-options-style-valid.js34
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/extensibility.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/length.js36
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/name.js32
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/newtarget-undefined.js30
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prop-desc.js35
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype.js23
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/constructor/prop-desc.js35
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/constructor/value.js15
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/branding.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-arguments-throws.js31
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-negative-duration-throws.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/length.js37
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/name.js29
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-default-en.js36
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-short-en.js38
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-digital-en.js38
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-long-en.js38
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-narrow-en.js38
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/not-a-constructor.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/numeric-hour-with-zero-minutes-and-non-zero-seconds.js44
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js96
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/shell.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-default-en.js31
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-en.js34
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits-undefined.js71
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits.js50
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-long-en.js33
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-narrow-en.js33
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/style-short-en.js33
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/format/throw-invoked-as-func.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/branding.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-default-en.js47
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-digital-en.js49
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-long-en.js49
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-narrow-en.js49
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-short-en.js49
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-arguments-throws.js40
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-negative-duration-throws.js23
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/length.js39
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/name.js29
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-default-en.js50
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-digital-en.js52
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-long-en.js52
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-narrow-en.js52
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-short-en.js52
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/not-a-constructor.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/shell.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/throw-invoked-as-func.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/prototype_attributes.js21
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/length.js39
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/name.js29
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/throw-invoked-as-func.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toString.js20
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toStringTag.js28
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/basic.js22
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/branding.js35
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/length.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-empty.js22
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-invalid.js23
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-specific.js25
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/name.js24
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/prop-desc.js32
-rw-r--r--js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/fails-on-distinct-temporal-types.js36
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/fails-on-distinct-temporal-types.js36
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/DateTimeFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/builtin.js21
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/Locale-object.js30
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-tags.js66
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-unicode-ext-seq.js41
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-language-subtag-replacement.js60
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js110
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/descriptor.js24
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/duplicates.js20
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/elements-not-reordered.js29
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/error-cases.js48
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/get-locale.js27
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/getCanonicalLocales.js26
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/grandfathered.js35
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/has-property.js32
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/invalid-tags.js32
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/length.js21
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/locales-is-not-a-string.js33
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/main.js34
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/name.js21
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/non-iana-canon.js81
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-arg-length.js103
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-push.js21
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-grandfathered.js98
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-variant.js60
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-an-array.js24
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-mutable.js49
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/to-string.js22
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-canonical.js56
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-invalid.js80
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-valid.js80
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-calendar.js60
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-col-strength.js67
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-measurement-system.js51
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-region.js69
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-subdivision.js74
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-timezone.js74
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js88
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-key-with-digit.js56
-rw-r--r--js/src/tests/test262/intl402/Intl/getCanonicalLocales/weird-cases.js26
-rw-r--r--js/src/tests/test262/intl402/Intl/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/builtin.js44
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DateTimeFormat.js47
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DisplayNames.js47
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars.js56
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/coerced-to-string.js38
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/collations-accepted-by-Collator.js83
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/collations.js58
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js53
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-NumberFormat.js46
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies.js50
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/invalid-key.js45
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/length.js32
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/name.js34
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-DateTimeFormat.js50
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-NumberFormat.js50
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-RelativeTimeFormat.js50
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js38
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems.js57
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/prop-desc.js25
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones-accepted-by-DateTimeFormat.js46
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones.js59
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/units-accepted-by-NumberFormat.js47
-rw-r--r--js/src/tests/test262/intl402/Intl/supportedValuesOf/units.js50
-rw-r--r--js/src/tests/test262/intl402/Intl/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Intl/toStringTag/toString.js34
-rw-r--r--js/src/tests/test262/intl402/Intl/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/ListFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-invalid.js18
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-valid.js45
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/newtarget-undefined.js29
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-getoptionsobject.js26
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-invalid.js19
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-localeMatcher-invalid.js31
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-order.js59
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-invalid.js34
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-valid.js35
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-throwing-getters.js27
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-invalid.js34
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-valid.js28
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-undefined.js40
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/proto-from-ctor-realm.js60
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/constructor/subclassing.js41
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/length.js24
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/name.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/prop-desc.js37
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/prototype.js19
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/basic.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/branding.js31
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/length.js24
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/locales-invalid.js22
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/name.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js36
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-null.js22
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-toobject.js43
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-undefined.js28
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/result-type.js35
-rw-r--r--js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/instance/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/instance/extensibility.js21
-rw-r--r--js/src/tests/test262/intl402/ListFormat/instance/prototype.js21
-rw-r--r--js/src/tests/test262/intl402/ListFormat/instance/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/constructor/prop-desc.js26
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/branding.js30
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-default.js54
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-disjunction.js56
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-narrow.js57
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-short.js56
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-unit.js56
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-long.js57
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-narrow.js57
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-short.js57
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-getiterator-throw.js29
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-invalid.js51
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorclose.js58
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorstep-throw.js44
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorvalue-throw.js47
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-undefined.js20
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/iterable.js47
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/length.js24
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/name.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/format/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/branding.js29
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-default.js89
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-disjunction.js89
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-narrow.js92
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-short.js91
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-unit.js91
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-long.js92
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-narrow.js92
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-short.js92
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-getiterator-throw.js30
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-invalid.js51
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorclose.js58
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorstep-throw.js45
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorvalue-throw.js47
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable.js47
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/length.js24
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/name.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/branding.js28
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/caching.js19
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/length.js23
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/name.js22
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/order.js28
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/type.js42
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toString.js18
-rw-r--r--js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/ListFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/canonicalize-locale-list-take-locale.js55
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-apply-options-canonicalizes-twice.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-getter-order.js127
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-locale-object.js39
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-newtarget-undefined.js25
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-non-iana-canon.js112
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-calendar-invalid.js38
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-calendar-valid.js43
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-canonicalized.js71
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-casefirst-invalid.js39
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-casefirst-valid.js68
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-collation-invalid.js36
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-collation-valid.js64
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-invalid.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-valid.js68
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-invalid.js45
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-valid.js74
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-language-grandfathered.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-language-invalid.js71
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-language-valid-undefined.js45
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-language-valid.js68
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-invalid.js43
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-valid.js63
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-numeric-undefined.js56
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-numeric-valid.js70
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-region-invalid.js58
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-region-valid.js69
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-script-invalid.js54
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-script-valid-undefined.js50
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-script-valid.js64
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-options-throwing-getters.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-parse-twice.js60
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-tag-tostring.js39
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-tag.js59
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-unicode-ext-invalid.js33
-rw-r--r--js/src/tests/test262/intl402/Locale/constructor-unicode-ext-valid.js40
-rw-r--r--js/src/tests/test262/intl402/Locale/extensions-grandfathered.js69
-rw-r--r--js/src/tests/test262/intl402/Locale/extensions-private.js26
-rw-r--r--js/src/tests/test262/intl402/Locale/function-prototype.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/getters-grandfathered.js41
-rw-r--r--js/src/tests/test262/intl402/Locale/getters-missing.js55
-rw-r--r--js/src/tests/test262/intl402/Locale/getters.js124
-rw-r--r--js/src/tests/test262/intl402/Locale/instance-extensibility.js22
-rw-r--r--js/src/tests/test262/intl402/Locale/instance.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws-boolean.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws-null.js21
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws-number.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws-symbol.js21
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws-undefined.js25
-rw-r--r--js/src/tests/test262/intl402/Locale/invalid-tag-throws.js41
-rw-r--r--js/src/tests/test262/intl402/Locale/length.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/likely-subtags-grandfathered.js200
-rw-r--r--js/src/tests/test262/intl402/Locale/likely-subtags.js115
-rw-r--r--js/src/tests/test262/intl402/Locale/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prop-desc.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/proto-from-ctor-realm.js60
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/baseName/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/baseName/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/baseName/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/baseName/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/baseName/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/calendar/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/calendar/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/calendar/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/calendar/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/calendar/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/caseFirst/branding.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/caseFirst/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/caseFirst/name.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/caseFirst/prop-desc.js29
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/caseFirst/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/collation/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/collation/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/collation/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/collation/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/collation/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/constructor/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/branding.js33
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/name.js25
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-id.js33
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-options.js54
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/output-array.js19
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCalendars/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array-values.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getCollations/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array-values.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getHourCycles/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/output-array.js19
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object-keys.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTextInfo/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-sorted.js25
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-undefined.js20
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array.js20
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getTimeZones/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/branding.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-id.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-option.js55
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js67
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/prop-desc.js28
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/hourCycle/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/hourCycle/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/hourCycle/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/hourCycle/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/hourCycle/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/language/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/language/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/language/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/language/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/language/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/branding.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/length.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/maximize/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/branding.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/length.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/name.js23
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/removing-likely-subtags-first-adds-likely-subtags.js51
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/minimize/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numberingSystem/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numberingSystem/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numberingSystem/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numberingSystem/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numberingSystem/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numeric/branding.js34
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numeric/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numeric/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numeric/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/numeric/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/region/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/region/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/region/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/region/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/region/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/script/branding.js32
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/script/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/script/name.js24
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/script/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/script/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toString/branding.js35
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toString/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString-removed-tag.js20
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString.js18
-rw-r--r--js/src/tests/test262/intl402/Locale/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/Locale/reject-duplicate-variants-in-tlang.js52
-rw-r--r--js/src/tests/test262/intl402/Locale/reject-duplicate-variants.js46
-rw-r--r--js/src/tests/test262/intl402/Locale/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Locale/subclassing.js28
-rw-r--r--js/src/tests/test262/intl402/Number/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/default-options-object-prototype.js21
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/length.js34
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/returns-same-results-as-NumberFormat.js40
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/shell.js24
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/taint-Intl-NumberFormat.js16
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/this-number-value.js28
-rw-r--r--js/src/tests/test262/intl402/Number/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js49
-rw-r--r--js/src/tests/test262/intl402/Number/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/builtin.js21
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/casing-numbering-system-options.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-compact.js46
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-no-compact.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-default-value.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-locales-arraylike.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-locales-get-tostring.js43
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-locales-hasproperty.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-locales-string.js27
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-locales-toobject.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-no-instanceof.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-notation.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-numberingSystem-order.js46
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-option-read-order.js57
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-numberingSystem-invalid.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-roundingMode-invalid.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-increment.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-mode.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-priority.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-trailing-zero-display.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-options-toobject.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-order.js30
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement-invalid.js46
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement.js52
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay-negative.js27
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay-invalid.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay.js36
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-unit.js65
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/constructor-unitDisplay.js68
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/currency-code-invalid.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/currency-code-well-formed.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/currency-digits.js191
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/currencyDisplay-unit.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/default-minimum-singificant-digits.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/default-options-object-prototype.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/dft-currency-mnfd-range-check-mxfd.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/fraction-digit-options-read-once.js20
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/ignore-invalid-unicode-ext-values.js38
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/instance-proto-and-extensible.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol-on-unwrap.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol.js20
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/legacy-regexp-statics-not-modified.js21
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/length.js36
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/name.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/numbering-system-options.js64
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/proto-from-ctor-realm.js60
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/builtin.js18
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/constructor/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/constructor/value.js14
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/bound-to-numberformat-instance.js37
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/builtin.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/default-value.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-de-DE.js78
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-en-US.js78
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ja-JP.js78
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ko-KR.js78
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-zh-TW.js78
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits-precision.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits.js57
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-builtin.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-length.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-name.js28
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-property-order.js18
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-max-min-fraction-significant-digits.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-negative-numbers.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-non-finite-numbers.js45
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-10.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-100.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1000.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-20.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-200.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2000.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-25.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-250.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2500.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-50.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-500.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5000.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-ceil.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-expand.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-floor.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-ceil.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-even.js36
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-expand.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-floor.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-trunc.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-trunc.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-auto.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-less-precision.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-more-precision.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits-precision.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits.js57
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/length.js36
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/name.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/no-instanceof.js26
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-de-DE.js47
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-en-US.js47
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ja-JP.js47
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ko-KR.js47
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-zh-TW.js47
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/numbering-systems.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/percent-formatter.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/prop-desc.js39
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/shell.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-de-DE.js62
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-en-US.js62
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ja-JP.js62
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ko-KR.js62
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-zh-TW.js62
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-de-DE.js77
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-en-US.js77
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ja-JP.js77
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ko-KR.js77
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-de-DE.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-en-US.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ja-JP.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ko-KR.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-zh-TW.js19
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-de-DE.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-en-US.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ja-JP.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ko-KR.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-zh-TW.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-rounding.js20
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-zh-TW.js77
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/this-value-not-numberformat.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-de-DE.js71
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-en-US.js71
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ja-JP.js71
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ko-KR.js71
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-zh-TW.js71
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/units-invalid.js110
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/units.js27
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-de-DE.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-IN.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-US.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-de-DE.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-US.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/value-arg-coerced-to-number.js26
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/value-decimal-string.js26
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/format/value-tonumber.js46
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/argument-to-Intlmathematicalvalue-throws.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/builtin.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/en-US.js39
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/invoked-as-func.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/length.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/name.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/nan-arguments-throws.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/prop-desc.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/pt-PT.js39
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/shell.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/undefined-arguments-throws.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/x-greater-than-y-not-throws.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/argument-to-Intlmathematicalvalue-throws.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/builtin.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/en-US.js67
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/length.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/name.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws.js31
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/prop-desc.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/shell.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/undefined-arguments-throws.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-not-throws.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/default-parameter.js44
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-de-DE.js88
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-en-US.js88
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ja-JP.js88
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ko-KR.js88
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-zh-TW.js88
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/length.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/main.js67
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/name.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-de-DE.js94
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-en-US.js94
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ja-JP.js90
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ko-KR.js90
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-zh-TW.js90
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js38
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/prop-desc.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-de-DE.js72
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-en-US.js72
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ja-JP.js72
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ko-KR.js72
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-zh-TW.js72
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-de-DE.js87
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-en-US.js87
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ja-JP.js87
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ko-KR.js87
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-de-DE.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-en-US.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ja-JP.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ko-KR.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-zh-TW.js55
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-de-DE.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-en-US.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ja-JP.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ko-KR.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-zh-TW.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-zh-TW.js87
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/this-value-not-numberformat.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-de-DE.js99
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-en-US.js99
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js99
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js99
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js99
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit.js27
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/value-tonumber.js52
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/basic.js45
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/compactDisplay.js26
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/length.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/name.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/no-instanceof.js26
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/order.js43
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/return-keys-order-default.js49
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/roundingMode.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/shell.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/this-value-not-numberformat.js23
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/this-value-numberformat-prototype.js17
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/configurable.js35
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/prop-desc.js18
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/significant-digits-options-get-sequence.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/style-unit.js28
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/subclassing.js30
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/basic.js25
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/length.js34
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/name.js29
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/shell.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/taint-Object-prototype.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/taint-Object-prototype.js18
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-currency.js57
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-currencyDisplay.js16
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-localeMatcher.js13
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority-mixed-options.js96
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority.js18
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-style.js14
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping-extended.js41
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping.js42
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/this-value-ignored.js37
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/throws-for-currency-style-without-currency-option.js22
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-over-limit.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-under-limit.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-over-limit.js24
-rw-r--r--js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-under-limit.js24
-rw-r--r--js/src/tests/test262/intl402/PluralRules/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/builtin.js21
-rw-r--r--js/src/tests/test262/intl402/PluralRules/can-be-subclassed.js30
-rw-r--r--js/src/tests/test262/intl402/PluralRules/constructor-option-read-order.js43
-rw-r--r--js/src/tests/test262/intl402/PluralRules/constructor-options-throwing-getters.js31
-rw-r--r--js/src/tests/test262/intl402/PluralRules/default-options-object-prototype.js20
-rw-r--r--js/src/tests/test262/intl402/PluralRules/internals.js19
-rw-r--r--js/src/tests/test262/intl402/PluralRules/length.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/name.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/PluralRules/proto-from-ctor-realm.js59
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/bind.js28
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/builtins.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/constructor/main.js14
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/constructor/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/properties.js17
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/prototype.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/builtins.js30
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/length.js34
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/name.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/order.js34
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/pluralCategories.js32
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/properties.js38
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/return-keys-order-default.js39
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/shell.js24
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/length.js34
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/name.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/non-finite.js24
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/select/tainting.js22
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/argument-tonumber-throws.js26
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/default-en-us.js16
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/invoked-as-func.js23
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/length.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/name.js19
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/nan-arguments-throws.js23
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/prop-desc.js23
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/undefined-arguments-throws.js22
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/selectRange/x-greater-than-y-not-throws.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-changed-tag.js30
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-removed-tag.js24
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString.js26
-rw-r--r--js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/PluralRules/shell.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/arguments.js16
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/length.js34
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/main.js25
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/name.js18
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/prop-desc.js33
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/shell.js24
-rw-r--r--js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/supportedLocalesOf.js30
-rw-r--r--js/src/tests/test262/intl402/PluralRules/undefined-newtarget-throws.js27
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-invalid.js20
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js45
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js29
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-invalid.js18
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-localeMatcher-invalid.js31
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-invalid.js46
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-valid.js29
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-invalid.js33
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-valid.js27
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-order.js68
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-proto.js90
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-invalid.js34
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-valid.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-throwing-getters.js119
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject-prototype.js37
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-undefined.js40
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/proto-from-ctor-realm.js59
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/subclassing.js42
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/length.js24
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/name.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prop-desc.js37
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prototype.js19
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/basic.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/branding.js34
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/length.js24
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/locales-invalid.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/name.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js36
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-null.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-toobject.js43
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-undefined.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/result-type.js35
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/instance/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/instance/extensibility.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/instance/prototype.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/instance/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/prop-desc.js26
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/branding.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js39
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js87
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js42
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/length.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/name.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js71
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js62
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js62
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-invalid.js55
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js42
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js38
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-symbol.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-tonumber.js40
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/branding.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-always.js107
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js156
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-style-short.js111
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/length.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/name.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-long.js141
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-narrow.js130
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-short.js130
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/result-type.js84
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-invalid.js55
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-plural.js54
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-non-finite.js38
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-symbol.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-tonumber.js52
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/branding.js28
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/caching.js19
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/length.js23
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/name.js22
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/order.js29
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/type.js42
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toString.js18
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toStringTag.js25
-rw-r--r--js/src/tests/test262/intl402/RelativeTimeFormat/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-invalid.js20
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-valid.js53
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/newtarget-undefined.js29
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-getoptionsobject.js28
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-abrupt-throws.js28
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-invalid.js42
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-toString-abrupt-throws.js59
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-valid.js32
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-invalid.js20
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-abrupt-throws.js34
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-invalid.js33
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-toString-abrupt-throws.js68
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-valid.js41
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-null.js26
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-order.js56
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-throwing-getters.js28
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-undefined.js52
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-valid-combinations.js39
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/proto-from-ctor-realm.js61
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/constructor/subclassing.js56
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/length.js25
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/name.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/prop-desc.js35
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/prototype.js20
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/basic.js22
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/branding.js35
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/length.js25
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-empty.js22
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-invalid.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-specific.js25
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/name.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-localeMatcher-invalid.js37
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-null.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-toobject.js44
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-undefined.js29
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/prop-desc.js32
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/result-type.js36
-rw-r--r--js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/ctor-custom-get-prototype-poison-throws.js37
-rw-r--r--js/src/tests/test262/intl402/Segmenter/ctor-custom-prototype.js34
-rw-r--r--js/src/tests/test262/intl402/Segmenter/ctor-default-prototype.js20
-rw-r--r--js/src/tests/test262/intl402/Segmenter/instance/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/instance/extensibility.js22
-rw-r--r--js/src/tests/test262/intl402/Segmenter/instance/prototype.js22
-rw-r--r--js/src/tests/test262/intl402/Segmenter/instance/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/proto-from-ctor-realm.js56
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/Symbol.toStringTag.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/constructor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/constructor/prop-desc.js27
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/constructor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/branding.js29
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/caching.js20
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/length.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/name.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/order.js30
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/prop-desc.js30
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/type-without-lbs.js39
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/branding.js29
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/branding.js28
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/breakable-input.js56
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/index-throws.js48
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/iswordlike.js59
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/length.js25
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/name.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/one-index.js70
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/out-of-bound-index.js56
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/unbreakable-input.js60
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/word-iswordlike.js58
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/zero-index.js72
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/length.js24
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/name.js23
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/nested-next.js39
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/next-inside-next.js50
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/next-mix-with-containing.js55
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/prop-desc.js31
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-grapheme-iterable.js57
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-sentence-iterable.js57
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-tostring.js37
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-word-iterable.js57
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/segment/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toString.js19
-rw-r--r--js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toStringTag.js26
-rw-r--r--js/src/tests/test262/intl402/Segmenter/shell.js0
-rw-r--r--js/src/tests/test262/intl402/String/browser.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/builtin.js30
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/default-options-object-prototype.js21
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/length.js34
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/missing-arguments-coerced-to-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/return-abrupt-this-value.js20
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/returns-same-results-as-Collator.js32
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/shell.js24
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/taint-Intl-Collator.js16
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/that-arg-coerced-to-string.js22
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/this-value-coerced-to-string.js22
-rw-r--r--js/src/tests/test262/intl402/String/prototype/localeCompare/throws-same-exceptions-as-Collator.js47
-rw-r--r--js/src/tests/test262/intl402/String/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/browser.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js17
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/shell.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js87
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js195
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js87
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/browser.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/shell.js0
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Azeri.js47
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Lithuanian.js115
-rw-r--r--js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Turkish.js47
-rw-r--r--js/src/tests/test262/intl402/String/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/calendar-case-insensitive.js14
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/from/basic.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/from/calendar-case-insensitive.js16
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/from/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/date-infinity-throws-rangeerror.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/one-of-era-erayear-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/order-of-operations.js79
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/argument-infinity-throws-rangeerror.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/until-across-lunisolar-leap-months.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/zero-length-duration-result.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-builtin-calendar-no-array-iteration.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-calendar-datefromfields-called-with-null-prototype-fields.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-constructor-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-duplicate-calendar-fields.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-leap-second.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-number.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-case-insensitive.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-leap-second.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-number.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-string.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-wrong-type.js46
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-year-zero.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-proto-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-calendar-annotation.js33
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-critical-unknown-annotation.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-date-with-utc-offset.js48
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-invalid.js64
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-calendar.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-time-zone.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-separators.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-zone-annotation.js38
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-unknown-annotation.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-with-utc-designator.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-wrong-type.js43
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-convert.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-slots.js40
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/builtin.js36
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/calendar-datefromfields-called-with-options-undefined.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/length.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/name.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/not-a-constructor.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/shell.js2182
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/year-zero.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-builtin-calendar-no-array-iteration.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-calendar-datefromfields-called-with-null-prototype-fields.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-constructor-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-duplicate-calendar-fields.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-leap-second.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-number.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-case-insensitive.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-leap-second.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-number.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-string.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-wrong-type.js46
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-year-zero.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-proto-in-calendar-fields.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-calendar-annotation.js33
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-critical-unknown-annotation.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-date-with-utc-offset.js48
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-invalid.js64
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-calendar.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-time-zone.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-separators.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-zone-annotation.js38
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-unknown-annotation.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-with-utc-designator.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-wrong-type.js43
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-convert.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-slots.js40
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/builtin.js36
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/calendar-datefromfields-called-with-options-undefined.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/length.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/name.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/prop-desc.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/shell.js2182
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/year-zero.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/gregorian-mutually-exclusive-fields.js108
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/japanese-mutually-exclusive-fields.js80
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/fields-underspecified.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/infinity-throws-rangeerror.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/order-of-operations.js79
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/reference-year-1972.js79
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/infinity-throws-rangeerror.js26
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/one-of-era-erayear-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/order-of-operations.js75
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/reference-day.js164
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Calendar/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-hour.js34
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-sub-minute-offset.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/compare/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/add/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-string-datetime.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-sub-minute-offset.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/add/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/round/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-string-datetime.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-sub-minute-offset.js42
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/round/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-string-datetime.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-sub-minute-offset.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/total/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-string-datetime.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-sub-minute-offset.js41
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/prototype/total/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/Duration/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/locales-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js50
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-offset.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/Instant/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDate/calendar-string.js15
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDate/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTime/calendar-string.js15
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/timezone-string-datetime.js63
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-string.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-timezone-string.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/timezone-string-datetime.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string-datetime.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/compare/infinity-throws-rangeerror.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/compare/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/from/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/locales-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js40
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/resolved-time-zone.js16
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js20
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDate/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/infinity-throws-rangeerror.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/from/infinity-throws-rangeerror.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/from/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/locales-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js50
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/resolved-time-zone.js33
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-always.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-auto.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-never.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-noniso.js57
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-id.js80
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-object.js64
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar.js57
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-iso-calendar.js55
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-missing-properties.js15
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-object.js36
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/reference-date-noniso-calendar.js20
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/locales-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/options-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/resolved-time-zone.js16
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/infinity-throws-rangeerror.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/fields-missing-properties.js15
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainMonthDay/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/locales-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js40
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/resolved-time-zone.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/plaindate-infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/timezone-string-datetime.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/infinity-throws-rangeerror.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/argument-object.js20
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/infinity-throws-rangeerror.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/locales-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-undefined.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/resolved-time-zone.js16
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/PlainYearMonth/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/basic.js32
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/etc-timezone.js69
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-object.js39
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-valid.js33
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/etc-timezone.js69
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/iana-legacy-names.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-case-insensitive.js628
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-string-datetime.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/iana-legacy-names.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-africa.js57
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-asia.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-australasia.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-backward.js143
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-backzone.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-etcetera.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-europe.js36
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/links-northamerica.js43
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/non-canonical-utc.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-object.js84
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-valid.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-iana-names.js50
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-not-equal.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/offset-and-iana.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/timezone-case-insensitive.js627
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/subtract-second-and-nanosecond-from-last-transition.js58
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/instant-string.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/nanoseconds-subtracted-or-added-at-dst-transition.js40
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/instant-string.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/basic.js81
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/dst.js28
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/instant-string.js35
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/nanoseconds-subtracted-or-added-at-dst-transition.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js29
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/supported-values-of.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/TimeZone/timezone-case-insensitive.js15
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-string-datetime.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js31
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js23
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/do-not-canonicalize-iana-identifiers.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js630
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/zoneddatetime-sub-minute-offset.js106
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/canonicalize-iana-identifiers-before-comparing.js21
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/custom-time-zone-ids-case-sensitive.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/sub-minute-offset.js38
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js25
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js17
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js54
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/canonicalize-iana-identifiers-before-comparing.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/custom-time-zone-ids-case-sensitive.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/sub-minute-offset.js51
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/custom-time-zone-name-not-supported.js20
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/offset-time-zone-not-supported.js14
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js51
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZone.js18
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZoneName-affects-instance-time-zone.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js30
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/time-zone-canonicalized.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js22
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/canonicalize-iana-identifiers-before-comparing.js19
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/custom-time-zone-ids-case-sensitive.js27
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/sub-minute-offset.js38
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/offset-property-sub-minute.js57
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js39
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js24
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js2158
-rw-r--r--js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/browser.js0
-rw-r--r--js/src/tests/test262/intl402/Temporal/shell.js0
-rw-r--r--js/src/tests/test262/intl402/TypedArray/browser.js0
-rw-r--r--js/src/tests/test262/intl402/TypedArray/prototype/browser.js0
-rw-r--r--js/src/tests/test262/intl402/TypedArray/prototype/shell.js0
-rw-r--r--js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/browser.js0
-rw-r--r--js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/calls-toLocaleString-number-elements.js25
-rw-r--r--js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/shell.js161
-rw-r--r--js/src/tests/test262/intl402/TypedArray/shell.js0
-rw-r--r--js/src/tests/test262/intl402/browser.js0
-rw-r--r--js/src/tests/test262/intl402/constructors-string-and-single-element-array.js73
-rw-r--r--js/src/tests/test262/intl402/constructors-taint-Object-prototype-2.js20
-rw-r--r--js/src/tests/test262/intl402/constructors-taint-Object-prototype.js20
-rw-r--r--js/src/tests/test262/intl402/default-locale-is-canonicalized.js18
-rw-r--r--js/src/tests/test262/intl402/default-locale-is-supported.js17
-rw-r--r--js/src/tests/test262/intl402/fallback-locales-are-supported.js37
-rw-r--r--js/src/tests/test262/intl402/language-tags-canonicalized.js55
-rw-r--r--js/src/tests/test262/intl402/language-tags-invalid.js24
-rw-r--r--js/src/tests/test262/intl402/language-tags-valid.js33
-rw-r--r--js/src/tests/test262/intl402/language-tags-with-underscore.js35
-rw-r--r--js/src/tests/test262/intl402/shell.js2717
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-consistent-with-resolvedOptions.js38
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-default-locale-and-zxx-locale.js26
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-duplicate-elements-removed.js19
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-empty-and-undefined.js21
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-coered-to-object.js33
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-empty-array.js21
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-returned-array-elements-are-not-frozen.js35
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-taint-Array-2.js25
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-taint-Array.js21
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-test-option-localeMatcher.js27
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-throws-if-element-not-string-or-object.js21
-rw-r--r--js/src/tests/test262/intl402/supportedLocalesOf-unicode-extensions-ignored.js39
1997 files changed, 186763 insertions, 0 deletions
diff --git a/js/src/tests/test262/intl402/Array/browser.js b/js/src/tests/test262/intl402/Array/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/browser.js
diff --git a/js/src/tests/test262/intl402/Array/prototype/browser.js b/js/src/tests/test262/intl402/Array/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Array/prototype/shell.js b/js/src/tests/test262/intl402/Array/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Array/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Array/prototype/toLocaleString/calls-toLocaleString-number-elements.js b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/calls-toLocaleString-number-elements.js
new file mode 100644
index 0000000000..bd1a389a64
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/calls-toLocaleString-number-elements.js
@@ -0,0 +1,19 @@
+// Copyright (C) 2018 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-array.prototype.tolocalestring
+description: >
+ Ensure "toLocaleString" is called with locale and options on number elements.
+---*/
+
+var n = 0;
+
+var locale = "th-u-nu-thai";
+var options = {
+ minimumFractionDigits: 3
+};
+
+assert.sameValue([n].toLocaleString(locale, options), n.toLocaleString(locale, options));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Array/prototype/toLocaleString/invoke-element-tolocalestring.js b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/invoke-element-tolocalestring.js
new file mode 100644
index 0000000000..5016ebec27
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/invoke-element-tolocalestring.js
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-array.prototype.tolocalestring
+description: >
+ The toLocaleString method of each non-undefined non-null element
+ must be called with two arguments.
+info: |
+ Array.prototype.toLocaleString ( [ _locales_ [ , _options_ ] ] )
+
+ 4. Let _R_ be the empty String.
+ 5. Let _k_ be 0.
+ 6. Repeat, while _k_ < _len_,
+ a. If _k_ > 0, then
+ i. Set _R_ to the string-concatenation of _R_ and _separator_.
+ b. Let _nextElement_ be ? Get(_array_, ! ToString(_k_)).
+ c. If _nextElement_ is not *undefined* or *null*, then
+ i. Let _S_ be ? ToString(? Invoke(_nextElement_, *"toLocaleString"*, « _locales_, _options_ »)).
+ ii. Set _R_ to the string-concatenation of _R_ and _S_.
+ d. Increase _k_ by 1.
+ 7. Return _R_.
+includes: [compareArray.js]
+---*/
+
+const unique = {
+ toString() {
+ return "<sentinel object>";
+ }
+};
+
+const testCases = [
+ { label: "no arguments", args: [], expectedArgs: [undefined, undefined] },
+ { label: "undefined locale", args: [undefined], expectedArgs: [undefined, undefined] },
+ { label: "string locale", args: ["ar"], expectedArgs: ["ar", undefined] },
+ { label: "object locale", args: [unique], expectedArgs: [unique, undefined] },
+ { label: "undefined locale and options", args: [undefined, unique], expectedArgs: [undefined, unique] },
+ { label: "string locale and options", args: ["zh", unique], expectedArgs: ["zh", unique] },
+ { label: "object locale and options", args: [unique, unique], expectedArgs: [unique, unique] },
+ { label: "extra arguments", args: [unique, unique, unique], expectedArgs: [unique, unique] },
+];
+
+for (const { label, args, expectedArgs } of testCases) {
+ assert.sameValue([undefined].toLocaleString(...args), "",
+ `must skip undefined elements when provided ${label}`);
+}
+for (const { label, args, expectedArgs } of testCases) {
+ assert.sameValue([null].toLocaleString(...args), "",
+ `must skip null elements when provided ${label}`);
+}
+
+for (const { label, args, expectedArgs } of testCases) {
+ const spy = {
+ toLocaleString(...receivedArgs) {
+ assert.compareArray(receivedArgs, expectedArgs,
+ `must invoke element toLocaleString with expected arguments when provided ${label}`);
+ return "ok";
+ }
+ };
+ assert.sameValue([spy].toLocaleString(...args), "ok");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Array/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Array/shell.js b/js/src/tests/test262/intl402/Array/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Array/shell.js
diff --git a/js/src/tests/test262/intl402/BigInt/browser.js b/js/src/tests/test262/intl402/BigInt/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/browser.js
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/browser.js b/js/src/tests/test262/intl402/BigInt/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/shell.js b/js/src/tests/test262/intl402/BigInt/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/builtin.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/builtin.js
new file mode 100644
index 0000000000..c3c87d4b85
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/builtin.js
@@ -0,0 +1,31 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ Tests that BigInt.prototype.toLocaleString meets the requirements
+ for built-in objects defined by the introduction of chapter 17 of
+ the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(BigInt.prototype.toLocaleString), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(BigInt.prototype.toLocaleString),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(BigInt.prototype.toLocaleString), Function.prototype);
+
+assert.sameValue(BigInt.prototype.toLocaleString.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(BigInt.prototype.toLocaleString), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/de-DE.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/de-DE.js
new file mode 100644
index 0000000000..b92250d0c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/de-DE.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: Checks basic behavior for BigInt.prototype.toLocaleString.
+locale: [de-DE]
+features: [BigInt]
+---*/
+
+const tests = [
+ [0n, undefined, "0"],
+ [-0n, undefined, "0"],
+ [88776655n, { "maximumSignificantDigits": 4 }, "88.780.000"],
+ [88776655n, { "maximumSignificantDigits": 4, "style": "percent" }, "8.878.000.000\u00a0%"],
+ [88776655n, { "minimumFractionDigits": 3 }, "88.776.655,000"],
+ [90071992547409910n, undefined, "90.071.992.547.409.910"],
+];
+
+for (const [bigint, options, expected] of tests) {
+ const result = bigint.toLocaleString("de-DE", options);
+ assert.sameValue(result, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/default-options-object-prototype.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/default-options-object-prototype.js
new file mode 100644
index 0000000000..893e074082
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/default-options-object-prototype.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for NumberFormat as a null prototype is used.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+locale: [en-US]
+features: [BigInt]
+---*/
+
+Object.prototype.useGrouping = false;
+assert.sameValue(12345n.toLocaleString("en-US"), "12,345");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/en-US.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/en-US.js
new file mode 100644
index 0000000000..96a0824ba9
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/en-US.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: Checks basic behavior for BigInt.prototype.toLocaleString.
+locale: [en-US]
+features: [BigInt]
+---*/
+
+const tests = [
+ [0n, undefined, "0"],
+ [-0n, undefined, "0"],
+ [88776655n, { "maximumSignificantDigits": 4 }, "88,780,000"],
+ [88776655n, { "maximumSignificantDigits": 4, "style": "percent" }, "8,878,000,000%"],
+ [88776655n, { "minimumFractionDigits": 3 }, "88,776,655.000"],
+ [90071992547409910n, undefined, "90,071,992,547,409,910"],
+];
+
+for (const [bigint, options, expected] of tests) {
+ const result = bigint.toLocaleString("en-US", options);
+ assert.sameValue(result, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/length.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/length.js
new file mode 100644
index 0000000000..6653858f1b
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/length.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ BigInt.prototype.toLocaleString.length is 0.
+info: |
+ BigInt.prototype.toLocaleString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [BigInt]
+---*/
+
+verifyProperty(BigInt.prototype.toLocaleString, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/name.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/name.js
new file mode 100644
index 0000000000..cab1cf03f4
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/name.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ BigInt.prototype.toLocaleString.name is toLocaleString.
+info: |
+ BigInt.prototype.toLocaleString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String. For functions that are specified as properties of objects,
+ the name value is the property name string used to access the function.
+
+ Unless otherwise specified, the name property of a built-in function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [BigInt]
+---*/
+
+verifyProperty(BigInt.prototype.toLocaleString, "name", {
+ value: "toLocaleString",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/prop-desc.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/prop-desc.js
new file mode 100644
index 0000000000..2b2c64d7d7
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/prop-desc.js
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: Checks the "toLocaleString" property of the BigInt prototype object.
+info: |
+ BigInt.prototype.toLocaleString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in
+ Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+ [[Configurable]]: true } unless otherwise specified.
+
+includes: [propertyHelper.js]
+features: [BigInt]
+---*/
+
+assert.sameValue(
+ typeof BigInt.prototype.toLocaleString,
+ "function",
+ "typeof BigInt.prototype.toLocaleString is function"
+);
+
+verifyProperty(BigInt.prototype, "toLocaleString", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/returns-same-results-as-NumberFormat.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/returns-same-results-as-NumberFormat.js
new file mode 100644
index 0000000000..5083fe05f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/returns-same-results-as-NumberFormat.js
@@ -0,0 +1,49 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ Tests that BigInt.prototype.toLocaleString produces the same
+ results as Intl.BigIntFormat.
+features: [BigInt]
+---*/
+
+var inputs = [
+ 0n,
+ -0n,
+ 1n,
+ -1n,
+ 123n,
+ -123n,
+ 12345n,
+ -12345n,
+ 12344501000000000000000000000000000n,
+ -12344501000000000000000000000000000n,
+];
+var localesInputs = [undefined, ["de"], ["th-u-nu-thai"], ["en"], ["ja-u-nu-jpanfin"], ["ar-u-nu-arab"]];
+var optionsInputs = [
+ undefined,
+ {style: "percent"},
+ {style: "currency", currency: "EUR", currencyDisplay: "symbol"},
+ {style: "currency", currency: "IQD", currencyDisplay: "symbol"},
+ {style: "currency", currency: "KMF", currencyDisplay: "symbol"},
+ {style: "currency", currency: "CLF", currencyDisplay: "symbol"},
+ {useGrouping: false, minimumIntegerDigits: 3, minimumFractionDigits: 1, maximumFractionDigits: 3}
+];
+
+for (const locales of localesInputs) {
+ for (const options of optionsInputs) {
+ const optionsString = options ? JSON.stringify(options) : String(options);
+ const referenceNumberFormat = new Intl.NumberFormat(locales, options);
+ for (const input of inputs) {
+ const referenceFormatted = referenceNumberFormat.format(input);
+ const formatted = input.toLocaleString(locales, options);
+ assert.sameValue(formatted, referenceFormatted,
+ `(Testing with locales ${locales}; options ${optionsString}.)`);
+ }
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/taint-Intl-NumberFormat.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/taint-Intl-NumberFormat.js
new file mode 100644
index 0000000000..f918e5537c
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/taint-Intl-NumberFormat.js
@@ -0,0 +1,17 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ Tests that Number.prototype.toLocaleString uses the standard
+ built-in Intl.NumberFormat constructor.
+includes: [testIntl.js]
+features: [BigInt]
+---*/
+
+taintDataProperty(Intl, "NumberFormat");
+0n.toLocaleString();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/this-value-invalid.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/this-value-invalid.js
new file mode 100644
index 0000000000..cfc9053033
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/this-value-invalid.js
@@ -0,0 +1,31 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: Tests that toLocaleString handles "thisBigIntValue" correctly.
+features: [BigInt]
+---*/
+
+var invalidValues = [
+ undefined,
+ null,
+ false,
+ "5",
+ Symbol(),
+ 5,
+ -1234567.89,
+ NaN,
+ -Infinity,
+ {valueOf: function () { return 5; }},
+ {valueOf: function () { return 5n; }},
+];
+
+for (const value of invalidValues) {
+ assert.throws(TypeError, function() {
+ BigInt.prototype.toLocaleString.call(value);
+ }, `BigInt.prototype.toLocaleString did not throw with this = ${String(value)}.`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js
new file mode 100644
index 0000000000..f4cb784959
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js
@@ -0,0 +1,50 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tolocalestring
+description: >
+ Tests that BigInt.prototype.toLocaleString throws the same
+ exceptions as Intl.NumberFormat.
+features: [BigInt]
+---*/
+
+var localesInputs = [null, [NaN], ["i"], ["de_DE"]];
+var optionsInputs = [
+ {localeMatcher: null},
+ {style: "invalid"},
+ {style: "currency"},
+ {style: "currency", currency: "ßP"},
+ {maximumSignificantDigits: -Infinity}
+];
+
+for (const locales of localesInputs) {
+ var referenceError, error;
+ try {
+ var format = new Intl.NumberFormat(locales);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.NumberFormat for locales " + locales + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = 0n.toLocaleString(locales);
+ }, "BigInt.prototype.toLocaleString didn't throw exception for locales " + locales + ".");
+}
+
+for (const options of optionsInputs) {
+ var referenceError, error;
+ try {
+ var format = new Intl.NumberFormat([], options);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.NumberFormat for options " + JSON.stringify(options) + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = 0n.toLocaleString([], options);
+ }, "BigInt.prototype.toLocaleString didn't throw exception for options " + JSON.stringify(options) + ".");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/BigInt/shell.js b/js/src/tests/test262/intl402/BigInt/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/BigInt/shell.js
diff --git a/js/src/tests/test262/intl402/Collator/browser.js b/js/src/tests/test262/intl402/Collator/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/builtin.js b/js/src/tests/test262/intl402/Collator/builtin.js
new file mode 100644
index 0000000000..ea9bf9465f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/builtin.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.1_L15
+description: >
+ Tests that Intl.Collator meets the requirements for built-in
+ objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.Collator), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.Collator), Function.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/constructor-options-throwing-getters.js b/js/src/tests/test262/intl402/Collator/constructor-options-throwing-getters.js
new file mode 100644
index 0000000000..ed2993524c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/constructor-options-throwing-getters.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: Checks the propagation of exceptions from the options for the Collator constructor.
+---*/
+
+function CustomError() {}
+
+const options = [
+ "usage",
+ "localeMatcher",
+ "collation",
+ "numeric",
+ "caseFirst",
+ "sensitivity",
+ "ignorePunctuation",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.Collator("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/default-options-object-prototype.js b/js/src/tests/test262/intl402/Collator/default-options-object-prototype.js
new file mode 100644
index 0000000000..7b699fe36a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/default-options-object-prototype.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for Collator as a null prototype is used.
+info: |
+ InitializeCollator ( collator, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+---*/
+
+let defaultSensitivity = new Intl.Collator("en").resolvedOptions().sensitivity;
+
+Object.prototype.sensitivity = "base";
+let collator = new Intl.Collator("en");
+assert.sameValue(collator.resolvedOptions().sensitivity, defaultSensitivity);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/ignore-invalid-unicode-ext-values.js b/js/src/tests/test262/intl402/Collator/ignore-invalid-unicode-ext-values.js
new file mode 100644
index 0000000000..0e36be1160
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/ignore-invalid-unicode-ext-values.js
@@ -0,0 +1,48 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.2.3_b
+description: >
+ Tests that Intl.Collator does not accept Unicode locale extension
+ keys and values that are not allowed.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+var testArray = [
+ "hello", "你好", "こんにちは",
+ "pêche", "peché", "1", "9", "10",
+ "ụ\u031B", "u\u031B\u0323", "ư\u0323", "u\u0323\u031B",
+ "Å", "Å", "A\u030A"
+];
+
+var defaultCollator = new Intl.Collator();
+var defaultOptions = defaultCollator.resolvedOptions();
+var defaultOptionsJSON = JSON.stringify(defaultOptions);
+var defaultLocale = defaultOptions.locale;
+var defaultSortedArray = testArray.slice(0).sort(defaultCollator.compare);
+
+var keyValues = {
+ "co": ["standard", "search", "invalid"],
+ "ka": ["noignore", "shifted", "invalid"],
+ "kb": ["true", "false", "invalid"],
+ "kc": ["true", "false", "invalid"],
+ "kh": ["true", "false", "invalid"],
+ "kk": ["true", "false", "invalid"],
+ "kr": ["latn-hira-hani", "hani-hira-latn", "invalid"],
+ "ks": ["level1", "level2", "level3", "level4", "identic", "invalid"],
+ "vt": ["1234-5678-9abc-edf0", "invalid"]
+};
+
+Object.getOwnPropertyNames(keyValues).forEach(function (key) {
+ keyValues[key].forEach(function (value) {
+ var collator = new Intl.Collator([defaultLocale + "-u-" + key + "-" + value]);
+ var options = collator.resolvedOptions();
+ assert.sameValue(options.locale, defaultLocale, "Locale " + options.locale + " is affected by key " + key + "; value " + value + ".");
+ assert.sameValue(JSON.stringify(options), defaultOptionsJSON, "Resolved options " + JSON.stringify(options) + " are affected by key " + key + "; value " + value + ".");
+ assert.compareArray(testArray.sort(collator.compare), defaultSortedArray);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/instance-proto-and-extensible.js b/js/src/tests/test262/intl402/Collator/instance-proto-and-extensible.js
new file mode 100644
index 0000000000..ba37de24f4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/instance-proto-and-extensible.js
@@ -0,0 +1,19 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.3
+description: >
+ Tests that objects constructed by Intl.Collator have the specified
+ internal properties.
+author: Norbert Lindenberg
+---*/
+
+var obj = new Intl.Collator();
+
+var actualPrototype = Object.getPrototypeOf(obj);
+assert.sameValue(actualPrototype, Intl.Collator.prototype, "Prototype of object constructed by Intl.Collator isn't Intl.Collator.prototype.");
+
+assert(Object.isExtensible(obj), "Object constructed by Intl.Collator must be extensible.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/legacy-regexp-statics-not-modified.js b/js/src/tests/test262/intl402/Collator/legacy-regexp-statics-not-modified.js
new file mode 100644
index 0000000000..9d1b16d6e5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/legacy-regexp-statics-not-modified.js
@@ -0,0 +1,17 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_a
+description: >
+ Tests that constructing a Collator doesn't create or modify
+ unwanted properties on the RegExp constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testForUnwantedRegExpChanges(function () {
+ new Intl.Collator("de-DE-u-co-phonebk");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/length.js b/js/src/tests/test262/intl402/Collator/length.js
new file mode 100644
index 0000000000..1f3c1c3db9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator
+description: >
+ Intl.Collator.length is 0.
+info: |
+ Intl.Collator ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/missing-unicode-ext-value-defaults-to-true.js b/js/src/tests/test262/intl402/Collator/missing-unicode-ext-value-defaults-to-true.js
new file mode 100644
index 0000000000..29be350876
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/missing-unicode-ext-value-defaults-to-true.js
@@ -0,0 +1,27 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.5_11_g_ii_2
+description: >
+ Tests that missing Unicode extension values default to true for
+ boolean keys.
+author: Norbert Lindenberg
+---*/
+
+var extensions = ["-u-co-phonebk-kn", "-u-kn-co-phonebk", "-u-co-phonebk-kn-true", "-u-kn-true-co-phonebk"];
+extensions.forEach(function (extension) {
+ var defaultLocale = new Intl.Collator().resolvedOptions().locale;
+ var collator = new Intl.Collator([defaultLocale + extension], {usage: "sort"});
+ var locale = collator.resolvedOptions().locale;
+ var numeric = collator.resolvedOptions().numeric;
+ if (numeric !== undefined) {
+ assert.sameValue(numeric, true, "Default value for \"kn\" should be true, but is " + numeric + ".");
+ assert.sameValue(locale.indexOf("-kn-false"), -1, "\"kn-false\" is returned in locale, but shouldn't be.");
+ assert.sameValue(locale.indexOf("-kn-true"), -1, "\"kn-true\" is returned in locale, but shouldn't be.");
+ assert.sameValue(locale.indexOf("-kn") >= 0, true, "\"kn\" should be returned in locale.");
+ }
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/name.js b/js/src/tests/test262/intl402/Collator/name.js
new file mode 100644
index 0000000000..aa573b49c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Collator
+description: >
+ Intl.Collator.name is "Collator".
+info: |
+ 10.1.2 Intl.Collator ([ locales [ , options ]])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator, "name", {
+ value: "Collator",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/numeric-and-caseFirst.js b/js/src/tests/test262/intl402/Collator/numeric-and-caseFirst.js
new file mode 100644
index 0000000000..6953fe1b08
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/numeric-and-caseFirst.js
@@ -0,0 +1,58 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_19_c
+description: >
+ Tests that the options numeric and caseFirst can be set through
+ either the locale or the options.
+author: Norbert Lindenberg
+---*/
+
+var options = [
+ {key: "kn", property: "numeric", type: "boolean", values: [true, false]},
+ {key: "kf", property: "caseFirst", type: "string", values: ["upper", "lower", "false"]}
+];
+
+options.forEach(function (option) {
+ var defaultLocale = new Intl.Collator().resolvedOptions().locale;
+ var collator, opt, result;
+
+ // find out which values are supported for a property in the default locale
+ var supportedValues = [];
+ option.values.forEach(function (value) {
+ opt = {};
+ opt[option.property] = value;
+ collator = new Intl.Collator([defaultLocale], opt);
+ result = collator.resolvedOptions()[option.property];
+ if (result !== undefined && supportedValues.indexOf(result) === -1) {
+ supportedValues.push(result);
+ }
+ });
+
+ // verify that the supported values can also be set through the locale
+ supportedValues.forEach(function (value) {
+ collator = new Intl.Collator([defaultLocale + "-u-" + option.key + "-" + value]);
+ result = collator.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Property " + option.property + " couldn't be set through locale extension key " + option.key + ".");
+ });
+
+ // verify that the options setting overrides the locale setting
+ supportedValues.forEach(function (value) {
+ var otherValue;
+ option.values.forEach(function (possibleValue) {
+ if (possibleValue !== value) {
+ otherValue = possibleValue;
+ }
+ });
+ if (otherValue !== undefined) {
+ opt = {};
+ opt[option.property] = value;
+ collator = new Intl.Collator([defaultLocale + "-u-" + option.key + "-" + otherValue], opt);
+ result = collator.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Options value for property " + option.property + " doesn't override locale extension key " + option.key + ".");
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prop-desc.js b/js/src/tests/test262/intl402/Collator/prop-desc.js
new file mode 100644
index 0000000000..66e8f3e576
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator-intro
+description: >
+ "Collator" property of Intl.
+info: |
+ Intl.Collator (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, "Collator", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/Collator/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..473b816dad
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/proto-from-ctor-realm.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.Collator ( [ locales [ , options ] ] )
+
+ 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
+ ...
+ 5. Let collator be ? OrdinaryCreateFromConstructor(newTarget, "%CollatorPrototype%", internalSlotsList).
+ 6. Return ? InitializeCollator(collator, locales, options).
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var col;
+
+newTarget.prototype = undefined;
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = true;
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = '';
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 1;
+col = Reflect.construct(Intl.Collator, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(col), other.Intl.Collator.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/browser.js b/js/src/tests/test262/intl402/Collator/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/builtin.js b/js/src/tests/test262/intl402/Collator/prototype/builtin.js
new file mode 100644
index 0000000000..d43c58095d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/builtin.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3_L15
+description: >
+ Tests that Intl.Collator.prototype meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert(Object.isExtensible(Intl.Collator.prototype), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.Collator.prototype), Object.prototype,
+ "Built-in prototype objects must have Object.prototype as their prototype.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/bound-to-collator-instance.js b/js/src/tests/test262/intl402/Collator/prototype/compare/bound-to-collator-instance.js
new file mode 100644
index 0000000000..a5d3716700
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/bound-to-collator-instance.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_1_c
+description: Tests that compare function is bound to its Intl.Collator.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+var strings = ["d", "O", "od", "oe", "of", "ö", "o\u0308", "X", "y", "Z", "Z.", "𠮷野家", "吉野家", "!A", "A", "b", "C"];
+var locales = [undefined, ["de"], ["de-u-co-phonebk"], ["en"], ["ja"], ["sv"]];
+var options = [
+ undefined,
+ {usage: "search"},
+ {sensitivity: "base", ignorePunctuation: true}
+];
+
+locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var collatorObj = new Intl.Collator(locales, options);
+ var compareFunc = collatorObj.compare;
+ var referenceSorted = strings.slice();
+ referenceSorted.sort(function (a, b) { return collatorObj.compare(a, b); });
+ var sorted = strings;
+ sorted.sort(compareFunc);
+ assert.compareArray(sorted, referenceSorted,
+ "(Testing with locales " + locales + "; options " +
+ (options ? JSON.stringify(options) : options) + ".)");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/browser.js b/js/src/tests/test262/intl402/Collator/prototype/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/builtin.js b/js/src/tests/test262/intl402/Collator/prototype/compare/builtin.js
new file mode 100644
index 0000000000..11ccc40a7b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/builtin.js
@@ -0,0 +1,32 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_L15
+description: >
+ Tests that the getter for Intl.Collator.prototype.compare meets
+ the requirements for built-in objects defined by the introduction
+ of chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var compareFn = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare").get;
+
+assert.sameValue(Object.prototype.toString.call(compareFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(compareFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(compareFn), Function.prototype);
+
+assert.sameValue(compareFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(compareFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/canonically-equivalent-strings.js b/js/src/tests/test262/intl402/Collator/prototype/compare/canonically-equivalent-strings.js
new file mode 100644
index 0000000000..a0994462b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/canonically-equivalent-strings.js
@@ -0,0 +1,69 @@
+// Copyright 2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_CS_a
+description: >
+ Tests that the function returned by
+ Intl.Collator.prototype.compare returns 0 when comparing Strings
+ that are considered canonically equivalent by the Unicode
+ standard.
+author: Norbert Lindenberg
+---*/
+
+var collator = new Intl.Collator();
+var pairs = [
+ // example from Unicode 5.0, section 3.7, definition D70
+ ["o\u0308", "ö"],
+ // examples from Unicode 5.0, chapter 3.11
+ ["ä\u0323", "a\u0323\u0308"],
+ ["a\u0308\u0323", "a\u0323\u0308"],
+ ["ạ\u0308", "a\u0323\u0308"],
+ ["ä\u0306", "a\u0308\u0306"],
+ ["ă\u0308", "a\u0306\u0308"],
+ // example from Unicode 5.0, chapter 3.12
+ ["\u1111\u1171\u11B6", "퓛"],
+ // examples from UTS 10, Unicode Collation Algorithm
+ ["Å", "Å"],
+ ["Å", "A\u030A"],
+ ["x\u031B\u0323", "x\u0323\u031B"],
+ ["ự", "ụ\u031B"],
+ ["ự", "u\u031B\u0323"],
+ ["ự", "ư\u0323"],
+ ["ự", "u\u0323\u031B"],
+ // examples from UAX 15, Unicode Normalization Forms
+ ["Ç", "C\u0327"],
+ ["q\u0307\u0323", "q\u0323\u0307"],
+ ["가", "\u1100\u1161"],
+ ["Å", "A\u030A"],
+ ["Ω", "Ω"],
+ ["Å", "A\u030A"],
+ ["ô", "o\u0302"],
+ ["ṩ", "s\u0323\u0307"],
+ ["ḋ\u0323", "d\u0323\u0307"],
+ ["ḋ\u0323", "ḍ\u0307"],
+ ["q\u0307\u0323", "q\u0323\u0307"],
+ // examples involving supplementary characters from UCD NormalizationTest.txt
+ ["\uD834\uDD5E", "\uD834\uDD57\uD834\uDD65"],
+ ["\uD87E\uDC2B", "北"]
+
+];
+var i;
+for (i = 0; i < pairs.length; i++) {
+ var pair = pairs[i];
+ assert.sameValue(collator.compare(pair[0], pair[1]), 0, "Collator.compare considers " + pair[0] + " (" + toU(pair[0]) + ") ≠ " + pair[1] + " (" + toU(pair[1]) + ").");
+}
+
+function toU(s) {
+ var result = "";
+ var escape = "\\u0000";
+ var i;
+ for (i = 0; i < s.length; i++) {
+ var hex = s.charCodeAt(i).toString(16);
+ result += escape.substring(0, escape.length - hex.length) + hex;
+ }
+ return result;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-builtin.js b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-builtin.js
new file mode 100644
index 0000000000..3b6ec70b35
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-builtin.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_1_a_L15
+description: >
+ Tests that the function returned by
+ Intl.Collator.prototype.compare meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var compareFn = new Intl.Collator().compare;
+
+assert.sameValue(Object.prototype.toString.call(compareFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(compareFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(compareFn), Function.prototype);
+
+assert.sameValue(compareFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(compareFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-length.js b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-length.js
new file mode 100644
index 0000000000..8fab13ccf5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-length.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.compare
+description: >
+ The length of the bound Collator compare function is 2.
+info: |
+ get Intl.Collator.prototype.compare
+
+ ...
+ 4. If collator.[[BoundCompare]] is undefined, then
+ a. Let F be a new built-in function object as defined in 10.3.4.
+ b. Let bc be BoundFunctionCreate(F, collator, « »).
+ c. Perform ! DefinePropertyOrThrow(bc, "length", PropertyDescriptor {[[Value]]: 2,
+ [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
+ ...
+
+includes: [propertyHelper.js]
+---*/
+
+var compareFn = new Intl.Collator().compare;
+
+verifyProperty(compareFn, "length", {
+ value: 2,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-name.js b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-name.js
new file mode 100644
index 0000000000..234ef6ad27
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-name.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Collator.prototype.compare
+description: >
+ The bound Collator compare function is an anonymous function.
+info: |
+ 10.3.3 get Intl.Collator.prototype.compare
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in function object, including constructors, has a `name`
+ property whose value is a String. Functions that are identified as
+ anonymous functions use the empty string as the value of the `name`
+ property.
+ Unless otherwise specified, the `name` property of a built-in function
+ object has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*,
+ [[Configurable]]: *true* }.
+includes: [propertyHelper.js]
+---*/
+
+var compareFn = new Intl.Collator().compare;
+
+verifyProperty(compareFn, "name", {
+ value: "", writable: false, enumerable: false, configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-property-order.js b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-property-order.js
new file mode 100644
index 0000000000..8ef4ba381d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/compare-function-property-order.js
@@ -0,0 +1,18 @@
+// Copyright (C) 2020 ExE Boss. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createbuiltinfunction
+description: Collator bound compare function property order
+info: |
+ Set order: "length", "name"
+includes: [compareArray.js]
+---*/
+
+var compareFn = new Intl.Collator().compare;
+
+assert.compareArray(
+ Object.getOwnPropertyNames(compareFn),
+ ['length', 'name']
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/ignorePunctuation.js b/js/src/tests/test262/intl402/Collator/prototype/compare/ignorePunctuation.js
new file mode 100644
index 0000000000..e4fc3b75f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/ignorePunctuation.js
@@ -0,0 +1,19 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-collator-comparestrings
+description: test CompareStrings sync with resolvedOptions().ignorePunctuation.
+locale: [en, th, ja]
+---*/
+// test on three locales, 'th' has different default.
+['en', 'th', 'ja'].forEach((locale) => {
+ [undefined, true, false].forEach((ignorePunctuation) => {
+ let col = new Intl.Collator(locale, {ignorePunctuation});
+ // if ignorePunctuation is true, the comparison will be 0
+ let expected = col.resolvedOptions().ignorePunctuation ? 0 : -1;
+ assert.sameValue(col.compare("", " "), expected, "Compare to space");
+ assert.sameValue(col.compare("", "*"), expected, "Compare to star");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/length.js b/js/src/tests/test262/intl402/Collator/prototype/compare/length.js
new file mode 100644
index 0000000000..3966c60a65
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/length.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.compare
+description: >
+ get Intl.Collator.prototype.compare.length is 0.
+info: |
+ get Intl.Collator.prototype.compare
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare");
+
+verifyProperty(desc.get, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/name.js b/js/src/tests/test262/intl402/Collator/prototype/compare/name.js
new file mode 100644
index 0000000000..05cad26fe3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/name.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Collator.prototype.compare
+description: >
+ get Intl.Collator.prototype.compare.name is "get compare".
+info: |
+ 10.3.3 get Intl.Collator.prototype.compare
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare");
+
+verifyProperty(desc.get, "name", {
+ value: "get compare",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-basic.js b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-basic.js
new file mode 100644
index 0000000000..8243ddfd44
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-basic.js
@@ -0,0 +1,24 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_CS_b_NN
+description: >
+ Tests that the compare function isn't entirely unreasonable. This
+ test is not normative.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// this test should be valid at least for the following locales
+var locales = ["de", "en", "es", "fr", "it"];
+locales = Intl.Collator.supportedLocalesOf(locales, {localeMatcher: "lookup"});
+locales.forEach(function (locale) {
+ var collator = new Intl.Collator([locale], {sensitivity: "variant", localeMatcher: "lookup"});
+ var a = ["L", "X", "C", "k", "Z", "H", "d", "m", "w", "A", "i", "f", "y", "E", "N", "V", "g", "J", "b"];
+ a.sort(collator.compare);
+ var expected = ["A", "b", "C", "d", "E", "f", "g", "H", "i", "J", "k", "L", "m", "N", "V", "w", "X", "y", "Z"];
+ assert.compareArray(a, expected);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-phonebook.js b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-phonebook.js
new file mode 100644
index 0000000000..47d330eba2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-phonebook.js
@@ -0,0 +1,24 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_CS_c_NN
+description: >
+ Tests that the compare function supports phonebook sorting if it
+ says it does. This test is not normative.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// this test should be valid at least for the following locales
+var locales = ["de-DE-u-co-phonebk", "de-u-co-phonebk"];
+var collator = new Intl.Collator(locales, {localeMatcher: "lookup"});
+if (locales.indexOf(collator.resolvedOptions().locale) !== -1) {
+ var a = ["A", "b", "Af", "Ab", "od", "off", "Ä", "ö"];
+ a.sort(collator.compare);
+ var expected = ["A", "Ab", "Ä", "Af", "b", "od", "ö", "off"];
+ assert.compareArray(a, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-sensitivity.js b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-sensitivity.js
new file mode 100644
index 0000000000..8c5e70ba87
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/non-normative-sensitivity.js
@@ -0,0 +1,36 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3.2_CS_d_NN
+description: >
+ Tests that the compare function supports different sensitivity
+ settings. This test is not normative.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// this test should be valid at least for the following locales
+var locales = ["de", "en", "es", "it"];
+locales = Intl.Collator.supportedLocalesOf(locales, {localeMatcher: "lookup"});
+locales.forEach(function (locale) {
+ var target = "Aa";
+ var input = ["Aa", "bA", "E", "b", "aA", "fC", "áÁ", "Aã"];
+ var expected = {
+ "base": ["Aa", "aA", "áÁ", "Aã"],
+ "accent": ["Aa", "aA"],
+ "case": ["Aa", "Aã"],
+ "variant": ["Aa"]
+ };
+ Object.getOwnPropertyNames(expected).forEach(function (sensitivity) {
+ var collator = new Intl.Collator([locale], {usage: "search",
+ sensitivity: sensitivity, localeMatcher: "lookup"});
+ var matches = input.filter(function (v) {
+ return collator.compare(v, target) === 0;
+ });
+ assert.compareArray(matches, expected[sensitivity]);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/prop-desc.js b/js/src/tests/test262/intl402/Collator/prototype/compare/prop-desc.js
new file mode 100644
index 0000000000..ad9a6d5174
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/prop-desc.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.resolvedoptions
+description: >
+ "compare" property of Intl.Collator.prototype.
+info: |
+ get Intl.Collator.prototype.compare
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+ If only a get accessor function is described, the set accessor function is the default
+ value, undefined. If only a set accessor is described the get accessor is the default
+ value, undefined.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare");
+
+assert.sameValue(desc.set, undefined);
+assert.sameValue(typeof desc.get, "function");
+
+verifyProperty(Intl.Collator.prototype, "compare", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/compare/shell.js b/js/src/tests/test262/intl402/Collator/prototype/compare/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/compare/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Collator/prototype/constructor/browser.js b/js/src/tests/test262/intl402/Collator/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/Collator/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..402a772656
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/constructor/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.constructor
+description: >
+ "constructor" property of Intl.Collator.prototype.
+info: |
+ Intl.Collator.prototype.constructor
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.prototype, "constructor", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/constructor/shell.js b/js/src/tests/test262/intl402/Collator/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/constructor/value.js b/js/src/tests/test262/intl402/Collator/prototype/constructor/value.js
new file mode 100644
index 0000000000..37cb53b0c5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/constructor/value.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3.1
+description: >
+ Tests that Intl.Collator.prototype.constructor is the
+ Intl.Collator.
+---*/
+
+assert.sameValue(Intl.Collator.prototype.constructor, Intl.Collator, "Intl.Collator.prototype.constructor is not the same as Intl.Collator");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/prop-desc.js b/js/src/tests/test262/intl402/Collator/prototype/prop-desc.js
new file mode 100644
index 0000000000..52e6fec6b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/prop-desc.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.2.1
+description: Tests that Intl.Collator.prototype has the required attributes.
+author: Norbert Lindenberg
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/basic.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/basic.js
new file mode 100644
index 0000000000..eb59055e28
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/basic.js
@@ -0,0 +1,50 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3.3
+description: >
+ Tests that the object returned by
+ Intl.Collator.prototype.resolvedOptions has the right properties.
+author: Norbert Lindenberg
+includes: [testIntl.js, propertyHelper.js]
+---*/
+
+var actual = new Intl.Collator().resolvedOptions();
+
+var actual2 = new Intl.Collator().resolvedOptions();
+assert.notSameValue(actual2, actual, "resolvedOptions returned the same object twice.");
+
+var collations = ["default", ...allCollations()];
+
+// this assumes the default values where the specification provides them
+assert(isCanonicalizedStructurallyValidLanguageTag(actual.locale),
+ "Invalid locale: " + actual.locale);
+assert.sameValue(actual.usage, "sort");
+assert.sameValue(actual.sensitivity, "variant");
+assert.sameValue(actual.ignorePunctuation, false);
+assert.notSameValue(actual.collation, "search");
+assert.notSameValue(actual.collation, "standard");
+assert.notSameValue(collations.indexOf(actual.collation), -1,
+ "Invalid collation: " + actual.collation);
+
+var dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+verifyProperty(actual, "locale", dataPropertyDesc);
+verifyProperty(actual, "usage", dataPropertyDesc);
+verifyProperty(actual, "sensitivity", dataPropertyDesc);
+verifyProperty(actual, "ignorePunctuation", dataPropertyDesc);
+verifyProperty(actual, "collation", dataPropertyDesc);
+
+// "numeric" is an optional property.
+if (actual.hasOwnProperty("numeric")) {
+ assert.notSameValue([true, false].indexOf(actual.numeric), -1);
+ verifyProperty(actual, "numeric", dataPropertyDesc);
+}
+
+// "caseFirst" is an optional property.
+if (actual.hasOwnProperty("caseFirst")) {
+ assert.notSameValue(["upper", "lower", "false"].indexOf(actual.caseFirst), -1);
+ verifyProperty(actual, "caseFirst", dataPropertyDesc);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/builtin.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/builtin.js
new file mode 100644
index 0000000000..6fef5829f6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.3.3_L15
+description: >
+ Tests that Intl.Collator.prototype.resolvedOptions meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator.prototype.resolvedOptions), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.Collator.prototype.resolvedOptions),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.Collator.prototype.resolvedOptions), Function.prototype);
+
+assert.sameValue(Intl.Collator.prototype.resolvedOptions.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.Collator.prototype.resolvedOptions), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/ignorePunctuation-not-default.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/ignorePunctuation-not-default.js
new file mode 100644
index 0000000000..ff77cf814e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/ignorePunctuation-not-default.js
@@ -0,0 +1,17 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializecollator
+description: resolved ignorePunctuation is the same as the one specified in option bag.
+locale: [en, th, ja]
+---*/
+['en', 'th', 'ja'].forEach((locale) => {
+ [true, false].forEach((ignorePunctuation) => {
+ assert.sameValue(
+ (new Intl.Collator(locale, {ignorePunctuation}))
+ .resolvedOptions().ignorePunctuation,
+ ignorePunctuation);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..ce9b4ff150
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.resolvedoptions
+description: >
+ Intl.Collator.prototype.resolvedOptions.length is 0.
+info: |
+ Intl.Collator.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..317f275e3b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Collator.prototype.resolvedOptions
+description: >
+ Intl.Collator.prototype.resolvedOptions.name is "resolvedOptions".
+info: |
+ 10.3.5 Intl.Collator.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..edc6adbc29
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/order.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+---*/
+
+const options = new Intl.Collator([], {
+ "numeric": true,
+ "caseFirst": "upper",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "usage",
+ "sensitivity",
+ "ignorePunctuation",
+ "collation",
+ "numeric",
+ "caseFirst"
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..5c947f53f8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype.resolvedoptions
+description: >
+ "resolvedOptions" property of Intl.Collator.prototype.
+info: |
+ Intl.Collator.prototype.resolvedOptions ()
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/resolvedOptions/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Collator/prototype/shell.js b/js/src/tests/test262/intl402/Collator/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/this-value-collator-prototype.js b/js/src/tests/test262/intl402/Collator/prototype/this-value-collator-prototype.js
new file mode 100644
index 0000000000..0ccb9a5f52
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/this-value-collator-prototype.js
@@ -0,0 +1,16 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-the-intl-collator-prototype-object
+description: >
+ Tests that Intl.Collator.prototype is not an object that has been
+ initialized as an Intl.Collator.
+---*/
+
+// test by calling a function that should fail as "this" is not an object
+// initialized as an Intl.Collator
+assert.throws(TypeError, () => Intl.Collator.prototype.compare("aаあ아", "aаあ아"),
+ "Intl.Collator.prototype is not an object that has been initialized as an Intl.Collator.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/this-value-not-collator.js b/js/src/tests/test262/intl402/Collator/prototype/this-value-not-collator.js
new file mode 100644
index 0000000000..dd75665f46
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/this-value-not-collator.js
@@ -0,0 +1,28 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.3_b
+description: >
+ Tests that Intl.Collator.prototype functions throw a TypeError if
+ called on a non-object value or an object that hasn't been
+ initialized as a Collator.
+author: Norbert Lindenberg
+---*/
+
+var functions = {
+ "compare getter": Object.getOwnPropertyDescriptor(Intl.Collator.prototype, "compare").get,
+ resolvedOptions: Intl.Collator.prototype.resolvedOptions
+};
+var invalidTargets = [undefined, null, true, 0, "Collator", [], {}];
+
+Object.getOwnPropertyNames(functions).forEach(function (functionName) {
+ var f = functions[functionName];
+ invalidTargets.forEach(function (target) {
+ assert.throws(TypeError, function() {
+ f.call(target);
+ }, "Calling " + functionName + " on " + target + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-changed-tag.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-changed-tag.js
new file mode 100644
index 0000000000..84dcbda908
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-changed-tag.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype-@@tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.Collator.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.Collator.prototype [ @@toStringTag ]
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+---*/
+
+Object.defineProperty(Intl.Collator.prototype, Symbol.toStringTag, {
+ value: "test262",
+});
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator.prototype), "[object test262]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Collator()), "[object test262]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-removed-tag.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-removed-tag.js
new file mode 100644
index 0000000000..16074d110f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString-removed-tag.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype-@@tostringtag
+description: >
+ Object.prototype.toString doesn't special-case neither Intl.Collator instances nor its prototype.
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+features: [Symbol.toStringTag]
+---*/
+
+delete Intl.Collator.prototype[Symbol.toStringTag];
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator.prototype), "[object Object]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Collator()), "[object Object]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..ad6395fee9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toString.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype-@@tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.Collator.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.Collator.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.Collator".
+features: [Symbol.toStringTag]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator.prototype), "[object Intl.Collator]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Collator()), "[object Intl.Collator]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..1f4942b46f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.prototype-@@tostringtag
+description: >
+ Property descriptor of Intl.Collator.prototype[@@toStringTag].
+info: |
+ Intl.Collator.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.Collator".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.prototype, Symbol.toStringTag, {
+ value: "Intl.Collator",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/shell.js b/js/src/tests/test262/intl402/Collator/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/shell.js
diff --git a/js/src/tests/test262/intl402/Collator/subclassing.js b/js/src/tests/test262/intl402/Collator/subclassing.js
new file mode 100644
index 0000000000..e6d499983d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/subclassing.js
@@ -0,0 +1,30 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.2_a
+description: Tests that Intl.Collator can be subclassed.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// get a collator and have it sort an array for comparison with the subclass
+var locales = ["tlh", "id", "en"];
+var a = ["A", "C", "E", "B", "D", "F"];
+var referenceCollator = new Intl.Collator(locales);
+var referenceSorted = a.slice().sort(referenceCollator.compare);
+
+class MyCollator extends Intl.Collator {
+ constructor(locales, options) {
+ super(locales, options);
+ // could initialize MyCollator properties
+ }
+ // could add methods to MyCollator.prototype
+}
+
+var collator = new MyCollator(locales);
+a.sort(collator.compare);
+assert.compareArray(a, referenceSorted);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..4ec8ea705a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/basic.js
@@ -0,0 +1,24 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.2.2_a
+description: >
+ Tests that Intl.Collator has a supportedLocalesOf property, and
+ it works as planned.
+---*/
+
+var defaultLocale = new Intl.Collator().resolvedOptions().locale;
+var notSupported = 'zxx'; // "no linguistic content"
+var requestedLocales = [defaultLocale, notSupported];
+
+var supportedLocales;
+
+assert(Intl.Collator.hasOwnProperty('supportedLocalesOf'), "Intl.Collator doesn't have a supportedLocalesOf property.");
+
+supportedLocales = Intl.Collator.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/builtin.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/builtin.js
new file mode 100644
index 0000000000..ab5f0f3e3f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.2.2_L15
+description: >
+ Tests that Intl.Collator.supportedLocalesOf meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Collator.supportedLocalesOf), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.Collator.supportedLocalesOf),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.Collator.supportedLocalesOf), Function.prototype);
+
+assert.sameValue(Intl.Collator.supportedLocalesOf.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.Collator.supportedLocalesOf), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..f61d6c706f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.supportedlocalesof
+description: >
+ Intl.Collator.supportedLocalesOf.length is 1.
+info: |
+ Intl.Collator.supportedLocalesOf ( locales [ , options ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..096f46d3c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Collator.supportedLocalesOf
+description: >
+ Intl.Collator.supportedLocalesOf.name is "supportedLocalesOf".
+info: |
+ 10.2.2 Intl.Collator.supportedLocalesOf (locales [ , options ])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..3d31e5314d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.collator.supportedlocalesof
+description: >
+ "supportedLocalesOf" property of Intl.Collator.
+info: |
+ Intl.Collator.supportedLocalesOf ( locales [ , options ] )
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.Collator, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Collator/supportedLocalesOf/taint-Object-prototype.js b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/taint-Object-prototype.js
new file mode 100644
index 0000000000..7e3018883f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/supportedLocalesOf/taint-Object-prototype.js
@@ -0,0 +1,16 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 10.2.2_b
+description: >
+ Tests that Intl.Collator.supportedLocalesOf doesn't access
+ arguments that it's not given.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Object.prototype, "1");
+new Intl.Collator("und");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/taint-Object-prototype.js b/js/src/tests/test262/intl402/Collator/taint-Object-prototype.js
new file mode 100644
index 0000000000..6832cb717d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/taint-Object-prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_10
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["localeMatcher", "kn", "kf"]);
+
+var locale = new Intl.Collator(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "Collator returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/test-option-ignorePunctuation.js b/js/src/tests/test262/intl402/Collator/test-option-ignorePunctuation.js
new file mode 100644
index 0000000000..0ffdbb1fd0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/test-option-ignorePunctuation.js
@@ -0,0 +1,14 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_23
+description: Tests that the option ignorePunctuation is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+// the fallback is variant only for usage === sort, but that happens to be the fallback for usage
+testOption(Intl.Collator, "ignorePunctuation", "boolean", undefined, false);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/test-option-localeMatcher.js b/js/src/tests/test262/intl402/Collator/test-option-localeMatcher.js
new file mode 100644
index 0000000000..76492a09de
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/test-option-localeMatcher.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_11
+description: Tests that the option localeMatcher is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.Collator, "localeMatcher", "string", ["lookup", "best fit"], "best fit", {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/test-option-numeric-and-caseFirst.js b/js/src/tests/test262/intl402/Collator/test-option-numeric-and-caseFirst.js
new file mode 100644
index 0000000000..186c1b8410
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/test-option-numeric-and-caseFirst.js
@@ -0,0 +1,16 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_13
+description: >
+ Tests that the options numeric and caseFirst are processed
+ correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.Collator, "numeric", "boolean", undefined, undefined, {isOptional: true});
+testOption(Intl.Collator, "caseFirst", "string", ["upper", "lower", "false"], undefined, {isOptional: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/test-option-sensitivity.js b/js/src/tests/test262/intl402/Collator/test-option-sensitivity.js
new file mode 100644
index 0000000000..382a6a84c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/test-option-sensitivity.js
@@ -0,0 +1,14 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_20
+description: Tests that the option sensitivity is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+// the fallback is variant only for usage === sort, but that happens to be the fallback for usage
+testOption(Intl.Collator, "sensitivity", "string", ["base", "accent", "case", "variant"], "variant");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/test-option-usage.js b/js/src/tests/test262/intl402/Collator/test-option-usage.js
new file mode 100644
index 0000000000..d504c081e7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/test-option-usage.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_6
+description: Tests that the option usage is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.Collator, "usage", "string", ["sort", "search"], "sort");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/this-value-ignored.js b/js/src/tests/test262/intl402/Collator/this-value-ignored.js
new file mode 100644
index 0000000000..36286fbd66
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/this-value-ignored.js
@@ -0,0 +1,32 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_1
+description: Tests that the this-value is ignored in Collator.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var obj, newObj;
+
+ // variant 1: use constructor in a "new" expression
+ obj = new Constructor();
+ newObj = Intl.Collator.call(obj);
+ assert.notSameValue(obj, newObj, "Collator object created with \"new\" was not ignored as this-value.");
+
+ // variant 2: use constructor as a function
+ if (Constructor !== Intl.Collator &&
+ Constructor !== Intl.NumberFormat &&
+ Constructor !== Intl.DateTimeFormat)
+ {
+ // Newer Intl constructors are not callable as a function.
+ return;
+ }
+ obj = Constructor();
+ newObj = Intl.Collator.call(obj);
+ assert.notSameValue(obj, newObj, "Collator object created with constructor as function was not ignored as this-value.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/unicode-ext-seq-in-private-tag.js b/js/src/tests/test262/intl402/Collator/unicode-ext-seq-in-private-tag.js
new file mode 100644
index 0000000000..2eb7546079
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/unicode-ext-seq-in-private-tag.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: >
+ Unicode extension sequence-like parts are ignored in private-use tags.
+info: |
+ 10.1.1 InitializeCollator ( collator, locales, options )
+ ...
+ 15. For each element key of relevantExtensionKeys in List order, do
+ a. If key is "co", then
+ i. Let value be r.[[co]].
+ ii. If value is null, let value be "default".
+ iii. Set collator.[[Collation]] to value.
+ ...
+
+ 10.3.5 Intl.Collator.prototype.resolvedOptions ()
+ The function returns a new object whose properties and attributes are set as if constructed
+ by an object literal assigning to each of the following properties the value of the
+ corresponding internal slot of this Collator object (see 10.4): ...
+---*/
+
+var c = new Intl.Collator("de-x-u-co-phonebk");
+var resolved = c.resolvedOptions();
+
+assert.sameValue(resolved.collation, "default");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/unicode-ext-seq-with-attribute.js b/js/src/tests/test262/intl402/Collator/unicode-ext-seq-with-attribute.js
new file mode 100644
index 0000000000..6b03babc6e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/unicode-ext-seq-with-attribute.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: >
+ Attributes in Unicode extension subtags should be ignored.
+info: |
+ 10.1.1 InitializeCollator ( collator, locales, options )
+ ...
+ 15. For each element key of relevantExtensionKeys in List order, do
+ a. If key is "co", then
+ i. Let value be r.[[co]].
+ ii. If value is null, let value be "default".
+ iii. Set collator.[[Collation]] to value.
+ ...
+
+ 10.3.5 Intl.Collator.prototype.resolvedOptions ()
+ The function returns a new object whose properties and attributes are set as if constructed
+ by an object literal assigning to each of the following properties the value of the
+ corresponding internal slot of this Collator object (see 10.4): ...
+---*/
+
+var colExpected = new Intl.Collator("de-u-attrval-co-phonebk");
+var colActual = new Intl.Collator("de-u-co-phonebk");
+
+var resolvedExpected = colExpected.resolvedOptions();
+var resolvedActual = colActual.resolvedOptions();
+
+assert.sameValue(resolvedActual.locale, resolvedExpected.locale);
+assert.sameValue(resolvedActual.collation, resolvedExpected.collation);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/unicode-ext-value-collation.js b/js/src/tests/test262/intl402/Collator/unicode-ext-value-collation.js
new file mode 100644
index 0000000000..c09eefec32
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/unicode-ext-value-collation.js
@@ -0,0 +1,40 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 10.1.1_19_b
+description: Tests the special handling of the "co" key in Intl.Collator.
+author: Norbert Lindenberg
+---*/
+
+function checkCollation(extensionCoValue, usageValue, expectedCollations, expectedUsage) {
+ var requestLocale = extensionCoValue !== undefined ? "de-DE-u-co-" + extensionCoValue : "de-DE";
+ var options = usageValue !== undefined ? { usage: usageValue } : undefined;
+ var collator = new Intl.Collator([requestLocale], options);
+
+ var collation = collator.resolvedOptions().collation;
+ assert.notSameValue(expectedCollations.indexOf(collation), -1, (extensionCoValue === undefined ? "Default collation" : "Collation for \"" + extensionCoValue) + "\" should be " + expectedCollations.join(" or ") + ", but is " + collation + ".");
+
+ var usage = collator.resolvedOptions().usage;
+ assert.sameValue(usage, expectedUsage, (usageValue === undefined ? "Default usage" : "Usage") + " mismatch.");
+}
+
+checkCollation(undefined, undefined, ["default"], "sort");
+
+checkCollation("phonebk", undefined, ["phonebk", "default"], "sort");
+
+checkCollation("invalid", undefined, ["default"], "sort");
+
+checkCollation("standard", undefined, ["default"], "sort");
+
+checkCollation("standard", "search", ["default"], "search");
+
+checkCollation("standard", "sort", ["default"], "sort");
+
+checkCollation("search", undefined, ["default"], "sort");
+
+checkCollation("search", "search", ["default"], "search");
+
+checkCollation("search", "sort", ["default"], "sort");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Collator/usage-de.js b/js/src/tests/test262/intl402/Collator/usage-de.js
new file mode 100644
index 0000000000..e0b01f5db6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Collator/usage-de.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: Checks the behavior of search and sort in German.
+includes: [compareArray.js]
+locale: [de]
+---*/
+
+assert.compareArray(["AE", "\u00C4"].sort(new Intl.Collator("de", {usage: "sort"}).compare),
+ ["\u00C4", "AE"],
+ "sort");
+assert.compareArray(["AE", "\u00C4"].sort(new Intl.Collator("de", {usage: "search"}).compare),
+ ["AE", "\u00C4"],
+ "search");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/browser.js b/js/src/tests/test262/intl402/Date/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/browser.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/browser.js b/js/src/tests/test262/intl402/Date/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/returns-same-results-as-DateTimeFormat.js b/js/src/tests/test262/intl402/Date/prototype/returns-same-results-as-DateTimeFormat.js
new file mode 100644
index 0000000000..0148c1c527
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/returns-same-results-as-DateTimeFormat.js
@@ -0,0 +1,57 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.3.0_7
+description: >
+ Tests that Date.prototype.toLocaleString & Co. produces the same
+ results as Intl.DateTimeFormat.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+var functions = {
+ toLocaleString: [Date.prototype.toLocaleString,
+ {year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"}],
+ toLocaleDateString: [Date.prototype.toLocaleDateString,
+ {year: "numeric", month: "numeric", day: "numeric"}],
+ toLocaleTimeString: [Date.prototype.toLocaleTimeString,
+ {hour: "numeric", minute: "numeric", second: "numeric"}]
+};
+var dates = [new Date(), new Date(0), new Date(Date.parse("1989-11-09T17:57:00Z"))];
+var locales = [undefined, ["de"], ["th-u-ca-gregory-nu-thai"], ["en"], ["ja-u-ca-japanese"], ["ar-u-ca-islamicc-nu-arab"]];
+var options = [
+ undefined,
+ {hour12: false},
+ {month: "long", day: "numeric", hour: "2-digit", minute: "2-digit"}
+];
+
+Object.getOwnPropertyNames(functions).forEach(function (p) {
+ var f = functions[p][0];
+ var defaults = functions[p][1];
+ locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var constructorOptions = options;
+ if (options === undefined) {
+ constructorOptions = defaults;
+ } else if (options.day === undefined) {
+ // for simplicity, our options above have either both date and time or neither
+ constructorOptions = Object.create(defaults);
+ for (var prop in options) {
+ if (options.hasOwnProperty(prop)) {
+ constructorOptions[prop] = options[prop];
+ }
+ }
+ }
+ var referenceDateTimeFormat = new Intl.DateTimeFormat(locales, constructorOptions);
+ var referenceFormatted = dates.map(referenceDateTimeFormat.format);
+
+ var formatted = dates.map(function (a) { return f.call(a, locales, options); });
+ assert.compareArray(formatted, referenceFormatted,
+ "(Testing with locales " + locales + "; options " +
+ (options ? JSON.stringify(options) : options) + ".)");
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/shell.js b/js/src/tests/test262/intl402/Date/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/taint-Intl-DateTimeFormat.js b/js/src/tests/test262/intl402/Date/prototype/taint-Intl-DateTimeFormat.js
new file mode 100644
index 0000000000..78775974bf
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/taint-Intl-DateTimeFormat.js
@@ -0,0 +1,18 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.3.0_6_2
+description: >
+ Tests that Date.prototype.toLocaleString & Co. use the standard
+ built-in Intl.DateTimeFormat constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Intl, "DateTimeFormat");
+new Date().toLocaleString();
+new Date().toLocaleDateString();
+new Date().toLocaleTimeString();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/this-value-invalid-date.js b/js/src/tests/test262/intl402/Date/prototype/this-value-invalid-date.js
new file mode 100644
index 0000000000..4b5c4b4ed9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/this-value-invalid-date.js
@@ -0,0 +1,27 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.3.0_2
+description: >
+ Tests that Date.prototype.toLocaleString & Co. handle non-finite
+ values correctly.
+author: Norbert Lindenberg
+---*/
+
+var functions = {
+ toLocaleString: Date.prototype.toLocaleString,
+ toLocaleDateString: Date.prototype.toLocaleDateString,
+ toLocaleTimeString: Date.prototype.toLocaleTimeString
+};
+var invalidValues = [NaN, Infinity, -Infinity];
+
+Object.getOwnPropertyNames(functions).forEach(function (p) {
+ var f = functions[p];
+ invalidValues.forEach(function (value) {
+ var result = f.call(new Date(value));
+ assert.sameValue(result, "Invalid Date", "Date.prototype." + p + " did not return \"Invalid Date\" for " + value);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/this-value-non-date.js b/js/src/tests/test262/intl402/Date/prototype/this-value-non-date.js
new file mode 100644
index 0000000000..51f1197f0a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/this-value-non-date.js
@@ -0,0 +1,28 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.3.0_1
+description: >
+ Tests that Date.prototype.toLocaleString & Co. handle "this time
+ value" correctly.
+author: Norbert Lindenberg
+---*/
+
+var functions = {
+ toLocaleString: Date.prototype.toLocaleString,
+ toLocaleDateString: Date.prototype.toLocaleDateString,
+ toLocaleTimeString: Date.prototype.toLocaleTimeString
+};
+var invalidValues = [undefined, null, 5, "5", false, {valueOf: function () { return 5; }}];
+
+Object.getOwnPropertyNames(functions).forEach(function (p) {
+ var f = functions[p];
+ invalidValues.forEach(function (value) {
+ assert.throws(TypeError, function() {
+ var result = f.call(value);
+ }, "Date.prototype." + p + " did not reject this = " + value + ".");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/throws-same-exceptions-as-DateTimeFormat.js b/js/src/tests/test262/intl402/Date/prototype/throws-same-exceptions-as-DateTimeFormat.js
new file mode 100644
index 0000000000..17371d2a85
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/throws-same-exceptions-as-DateTimeFormat.js
@@ -0,0 +1,56 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.3.0_6_1
+description: >
+ Tests that Date.prototype.toLocaleString & Co. throws the same
+ exceptions as Intl.DateTimeFormat.
+author: Norbert Lindenberg
+---*/
+
+var functions = {
+ toLocaleString: Date.prototype.toLocaleString,
+ toLocaleDateString: Date.prototype.toLocaleDateString,
+ toLocaleTimeString: Date.prototype.toLocaleTimeString
+};
+var locales = [null, [NaN], ["i"], ["de_DE"]];
+var options = [
+ {localeMatcher: null},
+ {timeZone: "invalid"},
+ {hour: "long"},
+ {formatMatcher: "invalid"}
+];
+
+Object.getOwnPropertyNames(functions).forEach(function (p) {
+ var f = functions[p];
+ locales.forEach(function (locales) {
+ var referenceError, error;
+ try {
+ var format = new Intl.DateTimeFormat(locales);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.DateTimeFormat for locales " + locales + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = f.call(new Date(), locales);
+ }, "Date.prototype." + p + " didn't throw exception for locales " + locales + ".");
+ });
+
+ options.forEach(function (options) {
+ var referenceError, error;
+ try {
+ var format = new Intl.DateTimeFormat([], options);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.DateTimeFormat for options " + JSON.stringify(options) + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = f.call(new Date(), [], options);
+ }, "Date.prototype." + p + " didn't throw exception for options " + JSON.stringify(options) + ".");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/browser.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/browser.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/builtin.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/builtin.js
new file mode 100644
index 0000000000..850f64fab5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.3.2_L15
+description: >
+ Tests that Date.prototype.toLocaleDateString meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Date.prototype.toLocaleDateString), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Date.prototype.toLocaleDateString),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Date.prototype.toLocaleDateString), Function.prototype);
+
+assert.sameValue(Date.prototype.toLocaleDateString.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Date.prototype.toLocaleDateString), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/length.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/length.js
new file mode 100644
index 0000000000..4c705597c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-date.prototype.tolocaledatestring
+description: >
+ Date.prototype.toLocaleDateString.length is 0.
+info: |
+ Date.prototype.toLocaleDateString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Date.prototype.toLocaleDateString, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/shell.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleDateString/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleString/builtin.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/builtin.js
new file mode 100644
index 0000000000..7db6c9b8df
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.3.1_L15
+description: >
+ Tests that Date.prototype.toLocaleString meets the requirements
+ for built-in objects defined by the introduction of chapter 17 of
+ the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Date.prototype.toLocaleString), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Date.prototype.toLocaleString),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Date.prototype.toLocaleString), Function.prototype);
+
+assert.sameValue(Date.prototype.toLocaleString.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Date.prototype.toLocaleString), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleString/default-options-object-prototype.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/default-options-object-prototype.js
new file mode 100644
index 0000000000..28654c8173
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/default-options-object-prototype.js
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-todatetimeoptions
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for DateTimeFormat as a null prototype is used.
+info: |
+ ToDateTimeOptions ( options, required, defaults )
+
+ 1. If options is undefined, let options be null; otherwise let options be ? ToObject(options).
+ 1. Let options be ObjectCreate(options).
+---*/
+
+if (new Intl.DateTimeFormat("en").resolvedOptions().locale === "en") {
+ Object.prototype.year = "2-digit";
+ assert.notSameValue(new Date().toLocaleString("en").length, 2);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleString/length.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/length.js
new file mode 100644
index 0000000000..5554f5285c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-date.prototype.tolocalestring
+description: >
+ Date.prototype.toLocaleString.length is 0.
+info: |
+ Date.prototype.toLocaleString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Date.prototype.toLocaleString, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleString/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/browser.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/browser.js
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/builtin.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/builtin.js
new file mode 100644
index 0000000000..e82ac0017e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.3.3_L15
+description: >
+ Tests that Date.prototype.toLocaleTimeString meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Date.prototype.toLocaleTimeString), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Date.prototype.toLocaleTimeString),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Date.prototype.toLocaleTimeString), Function.prototype);
+
+assert.sameValue(Date.prototype.toLocaleTimeString.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Date.prototype.toLocaleTimeString), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/length.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/length.js
new file mode 100644
index 0000000000..a5d99df0a8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-date.prototype.tolocaletimestring
+description: >
+ Date.prototype.toLocaleTimeString.length is 0.
+info: |
+ Date.prototype.toLocaleTimeString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Date.prototype.toLocaleTimeString, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/shell.js b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/prototype/toLocaleTimeString/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Date/shell.js b/js/src/tests/test262/intl402/Date/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Date/shell.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/builtin.js
new file mode 100644
index 0000000000..abffae0582
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/builtin.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.1_L15
+description: >
+ Tests that Intl.DateTimeFormat meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.DateTimeFormat), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.DateTimeFormat), Function.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/casing-numbering-system-calendar-options.js b/js/src/tests/test262/intl402/DateTimeFormat/casing-numbering-system-calendar-options.js
new file mode 100644
index 0000000000..15ae3a5e14
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/casing-numbering-system-calendar-options.js
@@ -0,0 +1,46 @@
+// Copyright 2020 Google Inc, Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that the options numberingSystem and calendar are mapped
+ to lower case properly.
+author: Caio Lima
+features: [Array.prototype.includes]
+---*/
+
+let defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
+
+let supportedNumberingSystems = ["latn", "arab"].filter(nu =>
+ new Intl.DateTimeFormat(defaultLocale + "-u-nu-" + nu)
+ .resolvedOptions().numberingSystem === nu
+);
+
+if (supportedNumberingSystems.includes("latn")) {
+ let dateTimeFormat = new Intl.DateTimeFormat(defaultLocale + "-u-nu-lATn");
+ assert.sameValue(dateTimeFormat.resolvedOptions().numberingSystem, "latn", "Numbering system option should be in lower case");
+}
+
+if (supportedNumberingSystems.includes("arab")) {
+ let dateTimeFormat = new Intl.DateTimeFormat(defaultLocale + "-u-nu-Arab");
+ assert.sameValue(dateTimeFormat.resolvedOptions().numberingSystem, "arab", "Numbering system option should be in lower case");
+}
+
+let supportedCalendars = ["gregory", "chinese"].filter(ca =>
+ new Intl.DateTimeFormat(defaultLocale + "-u-ca-" + ca)
+ .resolvedOptions().calendar === ca
+);
+
+if (supportedCalendars.includes("gregory")) {
+ let dateTimeFormat = new Intl.DateTimeFormat(defaultLocale + "-u-ca-Gregory");
+ assert.sameValue(dateTimeFormat.resolvedOptions().calendar, "gregory", "Calendar option should be in lower case");
+}
+
+if (supportedCalendars.includes("chinese")) {
+ let dateTimeFormat = new Intl.DateTimeFormat(defaultLocale + "-u-ca-CHINESE");
+ assert.sameValue(dateTimeFormat.resolvedOptions().calendar, "chinese", "Calendar option should be in lower case");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-calendar-numberingSystem-order.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-calendar-numberingSystem-order.js
new file mode 100644
index 0000000000..f7cecd54e7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-calendar-numberingSystem-order.js
@@ -0,0 +1,51 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks the order of getting "calendar" and "numberingSystem" options in the
+ DateTimeFormat is between "localeMatcher" and "hour12" options.
+info: |
+ 5. Let _matcher_ be ? GetOption(_options_, `"localeMatcher"`, ~string~, &laquo; `"lookup"`, `"best fit"` &raquo;, `"best fit"`).
+ ...
+ 7. Let _calendar_ be ? GetOption(_options_, `"calendar"`, ~string~ , ~empty~, *undefined*).
+ ...
+ 10. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`, ~string~, ~empty~, *undefined*).
+ ...
+ 13. Let _hour12_ be ? GetOption(_options_, `"hour12"`, ~boolean~, ~empty~, *undefined*).
+includes: [compareArray.js]
+---*/
+
+const actual = [];
+
+const options = {
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return undefined;
+ },
+ get calendar() {
+ actual.push("calendar");
+ return undefined;
+ },
+ get numberingSystem() {
+ actual.push("numberingSystem");
+ return undefined;
+ },
+ get hour12() {
+ actual.push("hour12");
+ return undefined;
+ },
+};
+
+const expected = [
+ "localeMatcher",
+ "calendar",
+ "numberingSystem",
+ "hour12"
+];
+
+let df = new Intl.DateTimeFormat(undefined, options);
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-default-value.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-default-value.js
new file mode 100644
index 0000000000..2cfcab904e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-default-value.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that the constructor for Intl.DateTimeFormat uses appropriate default
+ values for its arguments (locales and options).
+---*/
+
+const actual = new Intl.DateTimeFormat().resolvedOptions();
+const expected = new Intl.DateTimeFormat(
+ [],
+ Object.create(null)
+).resolvedOptions();
+
+assert.sameValue(actual.locale, expected.locale);
+assert.sameValue(actual.calendar, expected.calendar);
+assert.sameValue(actual.day, expected.day);
+assert.sameValue(actual.month, expected.month);
+assert.sameValue(actual.year, expected.year);
+assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+assert.sameValue(actual.timeZone, expected.timeZone);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-invalid-offset-timezone.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-invalid-offset-timezone.js
new file mode 100644
index 0000000000..d999f188a8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-invalid-offset-timezone.js
@@ -0,0 +1,38 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: Tests that invalid offset time zones are rejected.
+---*/
+let invalidOffsetTimeZones = [
+ '+3',
+ '+24',
+ '+23:0',
+ '+130',
+ '+13234',
+ '+135678',
+ '-7',
+ '-10.50',
+ '-10,50',
+ '-24',
+ '-014',
+ '-210',
+ '-2400',
+ '-1:10',
+ '-21:0',
+ '+0:003',
+ '+15:59:00',
+ '+15:59.50',
+ '+15:59,50',
+ '+222700',
+ '-02:3200',
+ '-170100',
+ '-22230',
+];
+invalidOffsetTimeZones.forEach((timeZone) => {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat(undefined, {timeZone});
+ }, timeZone + ":");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-no-instanceof.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-no-instanceof.js
new file mode 100644
index 0000000000..c2905aa57b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-no-instanceof.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl-datetimeformat-constructor
+description: >
+ Tests that the Intl.DateTimeFormat constructor calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ ChainDateTimeFormat ( dateTimeFormat, newTarget, this )
+ 1. If newTarget is undefined and ? OrdinaryHasInstance(%DateTimeFormat%, this) is true, then
+ a. Perform ? DefinePropertyOrThrow(this, %Intl%.[[FallbackSymbol]], PropertyDescriptor{
+ [[Value]]: dateTimeFormat, [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: false }).
+ b. Return this.
+---*/
+
+Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+Intl.DateTimeFormat();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-invalid.js
new file mode 100644
index 0000000000..894392f62c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-calendar-invalid.js
@@ -0,0 +1,42 @@
+// Copyright 2020 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+ 8. If calendar is not undefined, then
+ a. If calendar does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+---*/
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ numberingSystem = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidCalendarOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-gregory-",
+ "gregory-",
+ "gregory--",
+ "gregory-nu",
+ "gregory-nu-",
+ "gregory-nu-latn",
+ "gregoryé",
+ "gregory역법",
+];
+for (const calendar of invalidCalendarOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat('en', {calendar});
+ }, `new Intl.DateTimeFormat("en", {calendar: "${calendar}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-invalid.js
new file mode 100644
index 0000000000..0aac3d9863
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-invalid.js
@@ -0,0 +1,31 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+ 39. Let dateStyle be ? GetOption(options, "dateStyle", "string", « "full", "long", "medium", "short" », undefined).
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+
+const invalidOptions = [
+ "",
+ "FULL",
+ " long",
+ "short ",
+ "narrow",
+ "numeric",
+];
+for (const dateStyle of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat("en", { dateStyle });
+ }, `new Intl.DateTimeFormat("en", { dateStyle: "${dateStyle}" }) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-valid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-valid.js
new file mode 100644
index 0000000000..0f6dc77f22
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dateStyle-valid.js
@@ -0,0 +1,39 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks handling of the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+ 39. Let dateStyle be ? GetOption(options, "dateStyle", "string", « "full", "long", "medium", "short" », undefined).
+ 40. If dateStyle is not undefined, set dateTimeFormat.[[DateStyle]] to dateStyle.
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+
+const validOptions = [
+ [undefined, undefined],
+ ["full", "full"],
+ ["long", "long"],
+ ["medium", "medium"],
+ ["short", "short"],
+ [{ toString() { return "full"; } }, "full"],
+ [{ valueOf() { return "long"; }, toString: undefined }, "long"],
+];
+for (const [dateStyle, expected] of validOptions) {
+ const dtf = new Intl.DateTimeFormat("en", { dateStyle });
+ const options = dtf.resolvedOptions();
+ assert.sameValue(options.dateStyle, expected);
+ const propdesc = Object.getOwnPropertyDescriptor(options, "dateStyle");
+ if (expected === undefined) {
+ assert.sameValue(propdesc, undefined);
+ } else {
+ assert.sameValue(propdesc.value, expected);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-invalid.js
new file mode 100644
index 0000000000..f331b357d7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-invalid.js
@@ -0,0 +1,30 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ [[DayPeriod]] `"dayPeriod"` `"narrow"`, `"short"`, `"long"`
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const invalidOptions = [
+ "",
+ "LONG",
+ " long",
+ "short ",
+ "full",
+ "numeric",
+];
+for (const dayPeriod of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat("en", { dayPeriod });
+ }, `new Intl.DateTimeFormat("en", { dayPeriod: "${dayPeriod}" }) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-valid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-valid.js
new file mode 100644
index 0000000000..75acac41a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-dayPeriod-valid.js
@@ -0,0 +1,36 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks handling of the options argument to the DateTimeFormat constructor.
+info: |
+ [[DayPeriod]] `"dayPeriod"` `"narrow"`, `"short"`, `"long"`
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const validOptions = [
+ [undefined, undefined],
+ ["long", "long"],
+ ["short", "short"],
+ ["narrow", "narrow"],
+ [{ toString() { return "narrow"; } }, "narrow"],
+ [{ valueOf() { return "long"; }, toString: undefined }, "long"],
+];
+for (const [dayPeriod, expected] of validOptions) {
+ const dtf = new Intl.DateTimeFormat("en", { dayPeriod });
+ const options = dtf.resolvedOptions();
+ assert.sameValue(options.dayPeriod, expected);
+ const propdesc = Object.getOwnPropertyDescriptor(options, "dayPeriod");
+ if (expected === undefined) {
+ assert.sameValue(propdesc, undefined);
+ } else {
+ assert.sameValue(propdesc.value, expected);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-invalid.js
new file mode 100644
index 0000000000..cd9bc0a97f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-invalid.js
@@ -0,0 +1,39 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+ ...
+ 37. For each row of Table 7, except the header row, in table order, do
+ a. Let prop be the name given in the Property column of the row.
+ b. If prop is "fractionalSecondDigits", then
+ i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined).
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+
+const invalidOptions = [
+ "LONG",
+ " long",
+ "short ",
+ "full",
+ "numeric",
+ -1,
+ 4,
+ "4",
+ "-1",
+ -0.00001,
+ 3.000001,
+];
+for (const fractionalSecondDigits of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat("en", { fractionalSecondDigits });
+ },
+ `new Intl.DateTimeFormat("en", { fractionalSecondDigits: "${fractionalSecondDigits}" }) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-valid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-valid.js
new file mode 100644
index 0000000000..d66e4209cd
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-valid.js
@@ -0,0 +1,44 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks handling of the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+ ...
+ 37. For each row of Table 7, except the header row, in table order, do
+ a. Let prop be the name given in the Property column of the row.
+ b. If prop is "fractionalSecondDigits", then
+ i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined).
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+
+const validOptions = [
+ [undefined, undefined],
+ [1, 1],
+ ["1", 1],
+ [2, 2],
+ ["2", 2],
+ [3, 3],
+ ["3", 3],
+ [2.9, 2],
+ ["2.9", 2],
+ [1.00001, 1],
+ [{ toString() { return "3"; } }, 3],
+];
+for (const [fractionalSecondDigits, expected] of validOptions) {
+ const dtf = new Intl.DateTimeFormat("en", { fractionalSecondDigits });
+ const options = dtf.resolvedOptions();
+ assert.sameValue(options.fractionalSecondDigits, expected);
+ const propdesc = Object.getOwnPropertyDescriptor(options, "fractionalSecondDigits");
+ if (expected === undefined) {
+ assert.sameValue(propdesc, undefined);
+ } else {
+ assert.sameValue(propdesc.value, expected);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-invalid-explicit-components.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-invalid-explicit-components.js
new file mode 100644
index 0000000000..c435c211bd
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-invalid-explicit-components.js
@@ -0,0 +1,51 @@
+// Copyright 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Verifies that constructor throws when dateStyle is combined with explicit format components.
+info: |
+ CreateDateTimeFormat ( newTarget, locales, options, required, defaults )
+ ...
+ 39. Let dateStyle be ? GetOption(options, "dateStyle", string, « "full", "long", "medium", "short" », undefined).
+ 40. Set dateTimeFormat.[[DateStyle]] to dateStyle.
+ 41. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
+ 42. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
+ 43. If dateStyle is not undefined or timeStyle is not undefined, then
+
+ a. If hasExplicitFormatComponents is true, then
+ i. Throw a TypeError exception.
+---*/
+
+function optionsThrow(options, testName) {
+ assert.throws(TypeError, function() {
+ new Intl.DateTimeFormat(undefined, options);
+ }, testName + ":");
+}
+
+optionsThrow({dateStyle: "full", weekday: "long"}, "dateStyle-weekday");
+optionsThrow({dateStyle: "full", era: "long"}, "dateStyle-era");
+optionsThrow({dateStyle: "full", year: "numeric"}, "dateStyle-year");
+optionsThrow({dateStyle: "full", month: "numeric"}, "dateStyle-month");
+optionsThrow({dateStyle: "full", day: "numeric"}, "dateStyle-day");
+optionsThrow({dateStyle: "full", dayPeriod: "long"}, "dateStyle-dayPeriod");
+optionsThrow({dateStyle: "full", hour: "numeric"}, "dateStyle-hour");
+optionsThrow({dateStyle: "full", minute: "numeric"}, "dateStyle-minute");
+optionsThrow({dateStyle: "full", second: "numeric"}, "dateStyle-second");
+optionsThrow({dateStyle: "full", fractionalSecondDigits: 1}, "dateStyle-fractionalSecondDigits");
+optionsThrow({dateStyle: "full", timeZoneName: "short"}, "dateStyle-timeZoneName");
+
+optionsThrow({timeStyle: "full", weekday: "long"}, "timeStyle-weekday");
+optionsThrow({timeStyle: "full", era: "long"}, "timeStyle-era");
+optionsThrow({timeStyle: "full", year: "numeric"}, "timeStyle-year");
+optionsThrow({timeStyle: "full", month: "numeric"}, "timeStyle-month");
+optionsThrow({timeStyle: "full", day: "numeric"}, "timeStyle-day");
+optionsThrow({timeStyle: "full", dayPeriod: "long"}, "timeStyle-dayPeriod");
+optionsThrow({timeStyle: "full", hour: "numeric"}, "timeStyle-hour");
+optionsThrow({timeStyle: "full", minute: "numeric"}, "timeStyle-minute");
+optionsThrow({timeStyle: "full", second: "numeric"}, "timeStyle-second");
+optionsThrow({timeStyle: "full", fractionalSecondDigits: 1}, "timeStyle-fractionalSecondDigits");
+optionsThrow({timeStyle: "full", timeZoneName: "short"}, "timeStyle-timeZoneName");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-numberingSystem-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-numberingSystem-invalid.js
new file mode 100644
index 0000000000..c1b555c87e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-numberingSystem-invalid.js
@@ -0,0 +1,41 @@
+// Copyright 2020 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+ ...
+ 27. If numberingSystem is not undefined, then
+ a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+---*/
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ numberingSystem = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidNumberingSystemOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-latn-",
+ "latn-",
+ "latn--",
+ "latn-ca",
+ "latn-ca-",
+ "latn-ca-gregory",
+ "latné",
+ "latn编号",
+];
+for (const numberingSystem of invalidNumberingSystemOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat('en', {numberingSystem});
+ }, `new Intl.DateTimeFormat("en", {numberingSystem: "${numberingSystem}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-dayPeriod.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-dayPeriod.js
new file mode 100644
index 0000000000..c05bd1bee8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-dayPeriod.js
@@ -0,0 +1,52 @@
+// Copyright 2019 Googe Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the order of getting options of 'dayPeriod' for the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( newTarget, locales, options, required, defaults )
+ ...
+ 36. For each row of Table 7, except the header row, in table order, do
+ a. Let prop be the name given in the Property column of the row.
+ b. If prop is "fractionalSecondDigits", then
+ i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined).
+ c. Else,
+ i. Let values be a List whose elements are the strings given in the Values column of the row.
+ ii. Let value be ? GetOption(options, prop, string, values, undefined).
+ d. Set formatOptions.[[<prop>]] to value.
+ ...
+includes: [compareArray.js]
+features: [Intl.DateTimeFormat-dayPeriod]
+
+---*/
+
+// Just need to ensure dayPeriod are get between day and hour.
+const expected = [
+ // CreateDateTimeFormat step 36.
+ "day",
+ "dayPeriod",
+ "hour"
+];
+
+const actual = [];
+
+const options = {
+ get day() {
+ actual.push("day");
+ return "numeric";
+ },
+ get dayPeriod() {
+ actual.push("dayPeriod");
+ return "long";
+ },
+ get hour() {
+ actual.push("hour");
+ return "numeric";
+ },
+};
+
+new Intl.DateTimeFormat("en", options);
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-fractionalSecondDigits.js
new file mode 100644
index 0000000000..8fddf69f91
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-fractionalSecondDigits.js
@@ -0,0 +1,68 @@
+// Copyright 2019 Googe Inc. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the order of getting options of 'fractionalSecondDigits' for the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( newTarget, locales, options, required, defaults )
+ ...
+ 4. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 36. For each row of Table 7, except the header row, in table order, do
+ a. Let prop be the name given in the Property column of the row.
+ b. If prop is "fractionalSecondDigits", then
+ i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined).
+ c. Else,
+ i. Let values be a List whose elements are the strings given in the Values column of the row.
+ ii. Let value be ? GetOption(options, prop, string, values, undefined).
+ d. Set formatOptions.[[<prop>]] to value.
+ 37. Let matcher be ? GetOption(options, "formatMatcher", "string", « "basic", "best fit" », "best fit").
+ ...
+includes: [compareArray.js]
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+// Just need to ensure fractionalSecondDigits are get
+// between "second" and "timeZoneName".
+const expected = [
+ // CreateDateTimeFormat step 4.
+ "localeMatcher",
+ // CreateDateTimeFormat step 36.
+ "second",
+ "fractionalSecondDigits",
+ "timeZoneName",
+ // CreateDateTimeFormat step 37.
+ "formatMatcher",
+];
+
+const actual = [];
+
+const options = {
+ get second() {
+ actual.push("second");
+ return "numeric";
+ },
+ get fractionalSecondDigits() {
+ actual.push("fractionalSecondDigits");
+ return undefined;
+ },
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return undefined;
+ },
+ get timeZoneName() {
+ actual.push("timeZoneName");
+ return undefined;
+ },
+ get formatMatcher() {
+ actual.push("formatMatcher");
+ return undefined;
+ },
+};
+
+new Intl.DateTimeFormat("en", options);
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-timedate-style.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-timedate-style.js
new file mode 100644
index 0000000000..34e92ff711
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order-timedate-style.js
@@ -0,0 +1,127 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the order of getting options for the DateTimeFormat constructor.
+includes: [compareArray.js]
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+// To be merged into constructor-options-order.js when the feature is removed.
+
+const expected = [
+ // CreateDateTimeFormat step 4.
+ "localeMatcher",
+ // CreateDateTimeFormat step 12.
+ "hour12",
+ // CreateDateTimeFormat step 13.
+ "hourCycle",
+ // CreateDateTimeFormat step 29.
+ "timeZone",
+ // CreateDateTimeFormat step 36.
+ "weekday",
+ "era",
+ "year",
+ "month",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "timeZoneName",
+ "formatMatcher",
+ // CreateDateTimeFormat step 38.
+ "dateStyle",
+ // CreateDateTimeFormat step 40.
+ "timeStyle",
+];
+
+const actual = [];
+
+const options = {
+ get dateStyle() {
+ actual.push("dateStyle");
+ return undefined;
+ },
+
+ get day() {
+ actual.push("day");
+ return "numeric";
+ },
+
+ get era() {
+ actual.push("era");
+ return "long";
+ },
+
+ get formatMatcher() {
+ actual.push("formatMatcher");
+ return "best fit";
+ },
+
+ get hour() {
+ actual.push("hour");
+ return "numeric";
+ },
+
+ get hour12() {
+ actual.push("hour12");
+ return true;
+ },
+
+ get hourCycle() {
+ actual.push("hourCycle");
+ return "h24";
+ },
+
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return "best fit";
+ },
+
+ get minute() {
+ actual.push("minute");
+ return "numeric";
+ },
+
+ get month() {
+ actual.push("month");
+ return "numeric";
+ },
+
+ get second() {
+ actual.push("second");
+ return "numeric";
+ },
+
+ get timeStyle() {
+ actual.push("timeStyle");
+ return undefined;
+ },
+
+ get timeZone() {
+ actual.push("timeZone");
+ return "UTC";
+ },
+
+ get timeZoneName() {
+ actual.push("timeZoneName");
+ return "long";
+ },
+
+ get weekday() {
+ actual.push("weekday");
+ return "long";
+ },
+
+ get year() {
+ actual.push("year");
+ return "numeric";
+ },
+};
+
+new Intl.DateTimeFormat("en", options);
+
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order.js
new file mode 100644
index 0000000000..1b12975daf
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-order.js
@@ -0,0 +1,111 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the order of getting options for the DateTimeFormat constructor.
+includes: [compareArray.js]
+---*/
+
+const expected = [
+ // CreateDateTimeFormat step 4.
+ "localeMatcher",
+ // CreateDateTimeFormat step 12.
+ "hour12",
+ // CreateDateTimeFormat step 13.
+ "hourCycle",
+ // CreateDateTimeFormat step 29.
+ "timeZone",
+ // CreateDateTimeFormat step 36.
+ "weekday",
+ "era",
+ "year",
+ "month",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "timeZoneName",
+ // CreateDateTimeFormat step 37.
+ "formatMatcher",
+];
+
+const actual = [];
+
+const options = {
+ get day() {
+ actual.push("day");
+ return "numeric";
+ },
+
+ get era() {
+ actual.push("era");
+ return "long";
+ },
+
+ get formatMatcher() {
+ actual.push("formatMatcher");
+ return "best fit";
+ },
+
+ get hour() {
+ actual.push("hour");
+ return "numeric";
+ },
+
+ get hour12() {
+ actual.push("hour12");
+ return true;
+ },
+
+ get hourCycle() {
+ actual.push("hourCycle");
+ return "h24";
+ },
+
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return "best fit";
+ },
+
+ get minute() {
+ actual.push("minute");
+ return "numeric";
+ },
+
+ get month() {
+ actual.push("month");
+ return "numeric";
+ },
+
+ get second() {
+ actual.push("second");
+ return "numeric";
+ },
+
+ get timeZone() {
+ actual.push("timeZone");
+ return "UTC";
+ },
+
+ get timeZoneName() {
+ actual.push("timeZoneName");
+ return "long";
+ },
+
+ get weekday() {
+ actual.push("weekday");
+ return "long";
+ },
+
+ get year() {
+ actual.push("year");
+ return "numeric";
+ },
+};
+
+new Intl.DateTimeFormat("en", options);
+
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-style-conflict.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-style-conflict.js
new file mode 100644
index 0000000000..9d8419a8af
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-style-conflict.js
@@ -0,0 +1,47 @@
+// Copyright 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Conflicting properties of dateStyle/timeStyle must be rejected with a TypeError for the options argument to the DateTimeFormat constructor.
+info: |
+ InitializeDateTimeFormat ( dateTimeFormat, locales, options )
+
+ ...
+ 43. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. If hasExplicitFormatComponents is true, then
+ i. Throw a TypeError exception.
+ b. If required is date and timeStyle is not undefined, then
+ i. Throw a TypeError exception.
+ c. If required is time and dateStyle is not undefined, then
+ i. Throw a TypeError exception.
+---*/
+
+
+// Table 4 - Property column + example value from Values column
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "fractionalSecondDigits", 3 ],
+ [ "timeZoneName", "short" ],
+];
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ new Intl.DateTimeFormat("en", { [option]: value, dateStyle: "short" });
+ }, `new Intl.DateTimeFormat("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+
+ assert.throws(TypeError, function() {
+ new Intl.DateTimeFormat("en", { [option]: value, timeStyle: "short" });
+ }, `new Intl.DateTimeFormat("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-dayPeriod.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-dayPeriod.js
new file mode 100644
index 0000000000..79260c8ef7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-dayPeriod.js
@@ -0,0 +1,26 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the propagation of exceptions from the options for the DateTimeFormat constructor.
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+function CustomError() {}
+
+const options = [
+ "dayPeriod",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.DateTimeFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-fractionalSecondDigits.js
new file mode 100644
index 0000000000..2ff4078376
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-fractionalSecondDigits.js
@@ -0,0 +1,26 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the propagation of exceptions from the options for the DateTimeFormat constructor.
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+function CustomError() {}
+
+const options = [
+ "fractionalSecondDigits",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.DateTimeFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-timedate-style.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-timedate-style.js
new file mode 100644
index 0000000000..1e23c09bc2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters-timedate-style.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the propagation of exceptions from the options for the DateTimeFormat constructor.
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+// To be merged into constructor-options-throwing-getters.js when the feature is removed.
+
+function CustomError() {}
+
+const options = [
+ // CreateDateTimeFormat step 39
+ "dateStyle",
+ // CreateDateTimeFormat step 41
+ "timeStyle",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.DateTimeFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters.js
new file mode 100644
index 0000000000..2eead83306
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-throwing-getters.js
@@ -0,0 +1,33 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks the propagation of exceptions from the options for the DateTimeFormat constructor.
+---*/
+
+function CustomError() {}
+
+const options = [
+ "weekday", "year", "month", "day",
+ "hour", "minute", "second",
+ "localeMatcher",
+ "hour12",
+ "hourCycle",
+ "timeZone",
+ "era",
+ "timeZoneName",
+ "formatMatcher",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.DateTimeFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-invalid.js
new file mode 100644
index 0000000000..0bb3b18afa
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-invalid.js
@@ -0,0 +1,31 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks error cases for the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+
+ ...
+ 41. Let timeStyle be ? GetOption(options, "timeStyle", "string", « "full", "long", "medium", "short" », undefined).
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+
+const invalidOptions = [
+ "",
+ "FULL",
+ " long",
+ "short ",
+ "narrow",
+ "numeric",
+];
+for (const timeStyle of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DateTimeFormat("en", { timeStyle });
+ }, `new Intl.DateTimeFormat("en", { timeStyle: "${timeStyle}" }) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-valid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-valid.js
new file mode 100644
index 0000000000..f8bad0acda
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeStyle-valid.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks handling of the options argument to the DateTimeFormat constructor.
+info: |
+ CreateDateTimeFormat ( dateTimeFormat, locales, options, required, default )
+
+ ...
+ 41. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
+ 42. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+const validOptions = [
+ [undefined, undefined],
+ ["full", "full"],
+ ["long", "long"],
+ ["medium", "medium"],
+ ["short", "short"],
+ [{ toString() { return "full"; } }, "full"],
+ [{ valueOf() { return "long"; }, toString: undefined }, "long"],
+];
+for (const [timeStyle, expected] of validOptions) {
+ const dtf = new Intl.DateTimeFormat("en", { timeStyle });
+ const options = dtf.resolvedOptions();
+ assert.sameValue(options.timeStyle, expected);
+ const propdesc = Object.getOwnPropertyDescriptor(options, "timeStyle");
+ if (expected === undefined) {
+ assert.sameValue(propdesc, undefined);
+ } else {
+ assert.sameValue(propdesc.value, expected);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-invalid.js
new file mode 100644
index 0000000000..7c37e7c523
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-invalid.js
@@ -0,0 +1,30 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Invalid values for the `timeZoneName` option of the DateTimeFormat constructor
+features: [Intl.DateTimeFormat-extend-timezonename]
+---*/
+
+assert.throws(RangeError, function () {
+ new Intl.DateTimeFormat('en', { timeZoneName: '' });
+}, 'empty string');
+
+assert.throws(RangeError, function () {
+ new Intl.DateTimeFormat('en', { timeZoneName: 'short ' });
+}, '"short "');
+
+assert.throws(RangeError, function () {
+ new Intl.DateTimeFormat('en', { timeZoneName: ' long' });
+}, '" long"');
+
+assert.throws(RangeError, function () {
+ new Intl.DateTimeFormat('en', { timeZoneName: 'offset' });
+}, '"offset"');
+
+assert.throws(RangeError, function () {
+ new Intl.DateTimeFormat('en', { timeZoneName: 'generic' });
+}, '"generic"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js
new file mode 100644
index 0000000000..cdfc4dcd5c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js
@@ -0,0 +1,30 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Valid values for the `timeZoneName` option of the DateTimeFormat constructor
+features: [Intl.DateTimeFormat-extend-timezonename]
+---*/
+
+var dtf;
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'short' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'short');
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'long' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'long');
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'shortOffset' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'shortOffset');
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'longOffset' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'longOffset');
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'shortGeneric' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'shortGeneric');
+
+dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'longGeneric' });
+assert.sameValue(dtf.resolvedOptions().timeZoneName, 'longGeneric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-toobject.js b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-toobject.js
new file mode 100644
index 0000000000..6f449b023b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/constructor-options-toobject.js
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that Intl.DateTimeFormat contructor converts the options argument
+ to an object using `ToObject` (7.1.13).
+
+---*/
+
+const toObjectResults = [
+ [true, new Boolean(true)],
+ [42, new Number(42)],
+ ['foo', new String('foo')],
+ [{}, {}],
+ [Symbol(), Object(Symbol())]
+];
+
+// Test if ToObject is used to convert primitives to Objects.
+toObjectResults.forEach(pair => {
+ const [value, result] = pair;
+
+ const actual = new Intl.DateTimeFormat(['en-US'], value).resolvedOptions();
+ const expected = new Intl.DateTimeFormat(['en-US'], result).resolvedOptions();
+
+ assert.sameValue(actual.locale, expected.locale);
+ assert.sameValue(actual.calendar, expected.calendar);
+ assert.sameValue(actual.day, expected.day);
+ assert.sameValue(actual.month, expected.month);
+ assert.sameValue(actual.year, expected.year);
+ assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+ assert.sameValue(actual.timeZone, expected.timeZone);
+});
+
+// ToObject throws a TypeError for undefined and null, but it's not called
+// when options is undefined.
+assert.throws(TypeError, () => new Intl.DateTimeFormat(['en-US'], null));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/date-time-options.js b/js/src/tests/test262/intl402/DateTimeFormat/date-time-options.js
new file mode 100644
index 0000000000..bfe57ade97
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/date-time-options.js
@@ -0,0 +1,106 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_TDTO
+description: >
+ Tests that the set of options for the date and time components is
+ processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = [[], ["zh-Hans-CN"], ["hi-IN"], ["en-US"], ["id-ID"]];
+var dates = [new Date(), new Date(0), new Date(Date.parse("1989-11-09T17:57:00Z"))];
+
+function testWithDateTimeFormat(options, expected) {
+ locales.forEach(function (locales) {
+ var format = new Intl.DateTimeFormat(locales, options);
+ var resolvedOptions = format.resolvedOptions();
+ getDateTimeComponents().forEach(function (component) {
+ if (resolvedOptions.hasOwnProperty(component)) {
+ assert(expected.hasOwnProperty(component),
+ "Unrequested component " + component +
+ " added to expected subset " + JSON.stringify(expected) +
+ "; locales " + locales + ", options " +
+ (options ? JSON.stringify(options) : options) + ".");
+ } else {
+ assert.sameValue(expected.hasOwnProperty(component), false,
+ "Missing component " + component +
+ " from expected subset " + JSON.stringify(expected) +
+ "; locales " + locales + ", options " +
+ (options ? JSON.stringify(options) : options) + ".");
+ }
+ });
+ });
+}
+
+function testWithToLocale(f, options, expected) {
+ // expected can be either one subset or an array of possible subsets
+ if (expected.length === undefined) {
+ expected = [expected];
+ }
+ locales.forEach(function (locales) {
+ dates.forEach(function (date) {
+ var formatted = Date.prototype[f].call(date, locales, options);
+ var expectedStrings = [];
+ expected.forEach(function (expected) {
+ var referenceFormat = new Intl.DateTimeFormat(locales, expected);
+ expectedStrings.push(referenceFormat.format(date));
+ });
+ assert.notSameValue(expectedStrings.indexOf(formatted), -1,
+ "Function " + f + " did not return expected string for locales " +
+ locales + ", options " + (options? JSON.stringify(options) : options) +
+ "; expected " +
+ (expectedStrings.length === 1 ? expectedStrings[0] : "one of " + expectedStrings) +
+ ", got " + formatted + ".");
+ });
+ });
+}
+
+// any/date: steps 5a, 6a, 7a
+testWithDateTimeFormat(undefined, {year: "numeric", month: "numeric", day: "numeric"});
+
+// any/date: steps 5a, 6a
+testWithDateTimeFormat({year: "numeric", month: "numeric"}, {year: "numeric", month: "numeric"});
+
+// any/date: steps 5a, 6a
+testWithDateTimeFormat({hour: "numeric", minute: "numeric"}, {hour: "numeric", minute: "numeric"});
+
+// any/all: steps 5a, 6a, 7a, 8a
+testWithToLocale("toLocaleString", undefined, [
+ // the first one is not guaranteed to be supported; the second one is
+ {year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"},
+ {weekday: "short", year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"}
+]);
+
+// any/all: steps 5a, 6a
+testWithToLocale("toLocaleString", {year: "numeric", month: "numeric"}, {year: "numeric", month: "numeric"});
+
+// any/all: steps 5a, 6a
+testWithToLocale("toLocaleString", {hour: "numeric", minute: "numeric"}, {hour: "numeric", minute: "numeric"});
+
+// date/date: steps 5a, 7a
+testWithToLocale("toLocaleDateString", undefined, {year: "numeric", month: "numeric", day: "numeric"});
+
+// date/date: steps 5a
+testWithToLocale("toLocaleDateString", {year: "numeric", month: "numeric"}, {year: "numeric", month: "numeric"});
+
+// date/date: steps 5a, 7a
+testWithToLocale("toLocaleDateString", {hour: "numeric", minute: "numeric", second: "numeric"}, [
+ // the first one is not guaranteed to be supported; the second one is
+ {year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"},
+ {weekday: "short", year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"}
+]);
+
+// time/time: steps 6a, 8a
+testWithToLocale("toLocaleTimeString", undefined, {hour: "numeric", minute: "numeric", second: "numeric"});
+
+// time/time: steps 6a, 8a
+testWithToLocale("toLocaleTimeString", {weekday: "short", year: "numeric", month: "numeric", day: "numeric"},
+ {weekday: "short", year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"});
+
+// time/time: steps 6a
+testWithToLocale("toLocaleTimeString", {hour: "numeric", minute: "numeric"}, {hour: "numeric", minute: "numeric"});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/default-options-object-prototype.js b/js/src/tests/test262/intl402/DateTimeFormat/default-options-object-prototype.js
new file mode 100644
index 0000000000..854e5ff3f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/default-options-object-prototype.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-todatetimeoptions
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for DateTimeFormat as a null prototype is used.
+info: |
+ ToDateTimeOptions ( options, required, defaults )
+
+ 1. If options is undefined, let options be null; otherwise let options be ? ToObject(options).
+ 1. Let options be ObjectCreate(options).
+---*/
+
+let defaultYear = new Intl.DateTimeFormat("en").resolvedOptions().year;
+
+Object.prototype.year = "2-digit";
+let formatter = new Intl.DateTimeFormat("en");
+assert.sameValue(formatter.resolvedOptions().year, defaultYear);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/ignore-invalid-unicode-ext-values.js b/js/src/tests/test262/intl402/DateTimeFormat/ignore-invalid-unicode-ext-values.js
new file mode 100644
index 0000000000..34c9829741
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/ignore-invalid-unicode-ext-values.js
@@ -0,0 +1,39 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.2.3_b
+description: >
+ Tests that Intl.DateTimeFormat does not accept Unicode locale
+ extension keys and values that are not allowed.
+author: Norbert Lindenberg
+---*/
+
+var locales = ["ja-JP", "zh-Hans-CN", "zh-Hant-TW"];
+var input = new Date(Date.parse("1989-11-09T17:57:00Z"));
+
+locales.forEach(function (locale) {
+ var defaultDateTimeFormat = new Intl.DateTimeFormat([locale]);
+ var defaultOptions = defaultDateTimeFormat.resolvedOptions();
+ var defaultOptionsJSON = JSON.stringify(defaultOptions);
+ var defaultLocale = defaultOptions.locale;
+ var defaultFormatted = defaultDateTimeFormat.format(input);
+
+ var keyValues = {
+ "cu": ["USD", "EUR", "JPY", "CNY", "TWD", "invalid"], // DateTimeFormat internally uses NumberFormat
+ "nu": ["native", "traditio", "finance", "invalid"],
+ "tz": ["usnavajo", "utcw01", "aumel", "uslax", "usnyc", "deber", "invalid"]
+ };
+
+ Object.getOwnPropertyNames(keyValues).forEach(function (key) {
+ keyValues[key].forEach(function (value) {
+ var dateTimeFormat = new Intl.DateTimeFormat([locale + "-u-" + key + "-" + value]);
+ var options = dateTimeFormat.resolvedOptions();
+ assert.sameValue(options.locale, defaultLocale, "Locale " + options.locale + " is affected by key " + key + "; value " + value + ".");
+ assert.sameValue(JSON.stringify(options), defaultOptionsJSON, "Resolved options " + JSON.stringify(options) + " are affected by key " + key + "; value " + value + ".");
+ assert.sameValue(dateTimeFormat.format(input), defaultFormatted, "Formatted value " + dateTimeFormat.format(input) + " is affected by key " + key + "; value " + value + ".");
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/instance-proto-and-extensible.js b/js/src/tests/test262/intl402/DateTimeFormat/instance-proto-and-extensible.js
new file mode 100644
index 0000000000..34e6873529
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/instance-proto-and-extensible.js
@@ -0,0 +1,19 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.3
+description: >
+ Tests that objects constructed by Intl.DateTimeFormat have the
+ specified internal properties.
+author: Norbert Lindenberg
+---*/
+
+var obj = new Intl.DateTimeFormat();
+
+var actualPrototype = Object.getPrototypeOf(obj);
+assert.sameValue(actualPrototype, Intl.DateTimeFormat.prototype, "Prototype of object constructed by Intl.DateTimeFormat isn't Intl.DateTimeFormat.prototype.");
+
+assert(Object.isExtensible(obj), "Object constructed by Intl.DateTimeFormat must be extensible.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol-on-unwrap.js b/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol-on-unwrap.js
new file mode 100644
index 0000000000..61b9452d9a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol-on-unwrap.js
@@ -0,0 +1,34 @@
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-unwrapdatetimeformat
+description: >
+ Tests that [[FallbackSymbol]]'s [[Description]] is "IntlLegacyConstructedSymbol" if normative optional is implemented.
+author: Yusuke Suzuki
+features: [intl-normative-optional]
+---*/
+
+let object = new Intl.DateTimeFormat();
+let newObject = Intl.DateTimeFormat.call(object);
+let symbol = null;
+let error = null;
+try {
+ let proxy = new Proxy(newObject, {
+ get(target, property) {
+ symbol = property;
+ return target[property];
+ }
+ });
+ Intl.DateTimeFormat.prototype.resolvedOptions.call(proxy);
+} catch (e) {
+ // If normative optional is not implemented, an error will be thrown.
+ error = e;
+ assert(error instanceof TypeError);
+}
+if (error === null) {
+ assert.sameValue(typeof symbol, "symbol");
+ assert.sameValue(symbol.description, "IntlLegacyConstructedSymbol");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol.js b/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol.js
new file mode 100644
index 0000000000..6ca345b44e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/intl-legacy-constructed-symbol.js
@@ -0,0 +1,20 @@
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat
+description: >
+ Tests that [[FallbackSymbol]]'s [[Description]] is "IntlLegacyConstructedSymbol" if normative optional is implemented.
+author: Yusuke Suzuki
+features: [intl-normative-optional]
+---*/
+
+let object = new Intl.DateTimeFormat();
+let newObject = Intl.DateTimeFormat.call(object);
+let symbols = Object.getOwnPropertySymbols(newObject);
+if (symbols.length !== 0) {
+ assert.sameValue(symbols.length, 1);
+ assert.sameValue(symbols[0].description, "IntlLegacyConstructedSymbol");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/legacy-regexp-statics-not-modified.js b/js/src/tests/test262/intl402/DateTimeFormat/legacy-regexp-statics-not-modified.js
new file mode 100644
index 0000000000..6d44737ef4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/legacy-regexp-statics-not-modified.js
@@ -0,0 +1,21 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_a
+description: >
+ Tests that constructing a DateTimeFormat doesn't create or modify
+ unwanted properties on the RegExp constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testForUnwantedRegExpChanges(function () {
+ new Intl.DateTimeFormat("de-DE-u-ca-gregory");
+});
+
+testForUnwantedRegExpChanges(function () {
+ new Intl.DateTimeFormat("de-DE-u-ca-gregory", {timeZone: "UTC"});
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/length.js b/js/src/tests/test262/intl402/DateTimeFormat/length.js
new file mode 100644
index 0000000000..29563adcd3
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat
+description: >
+ Intl.DateTimeFormat.length is 0.
+info: |
+ Intl.DateTimeFormat ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/name.js b/js/src/tests/test262/intl402/DateTimeFormat/name.js
new file mode 100644
index 0000000000..fbdc7f6cab
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat
+description: >
+ Intl.DateTimeFormat.name is "DateTimeFormat".
+info: |
+ 12.2.1 Intl.DateTimeFormat ([ locales [ , options ]])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat, "name", {
+ value: "DateTimeFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/numbering-system-calendar-options.js b/js/src/tests/test262/intl402/DateTimeFormat/numbering-system-calendar-options.js
new file mode 100644
index 0000000000..baa730596c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/numbering-system-calendar-options.js
@@ -0,0 +1,69 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that the options numberingSystem and calendar can be set through
+ either the locale or the options.
+author: Norbert Lindenberg, Daniel Ehrenberg
+---*/
+
+let defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
+
+let supportedNumberingSystems = ["latn", "arab"].filter(nu =>
+ new Intl.DateTimeFormat(defaultLocale + "-u-nu-" + nu)
+ .resolvedOptions().numberingSystem === nu
+);
+
+let supportedCalendars = ["gregory", "chinese"].filter(ca =>
+ new Intl.DateTimeFormat(defaultLocale + "-u-ca-" + ca)
+ .resolvedOptions().calendar === ca
+);
+
+let options = [
+ {key: "nu", property: "numberingSystem", type: "string", values: supportedNumberingSystems},
+ {key: "ca", property: "calendar", type: "string", values: supportedCalendars}
+];
+
+options.forEach(function (option) {
+ let dateTimeFormat, opt, result;
+
+ // find out which values are supported for a property in the default locale
+ let supportedValues = [];
+ option.values.forEach(function (value) {
+ opt = {};
+ opt[option.property] = value;
+ dateTimeFormat = new Intl.DateTimeFormat([defaultLocale], opt);
+ result = dateTimeFormat.resolvedOptions()[option.property];
+ if (result !== undefined && supportedValues.indexOf(result) === -1) {
+ supportedValues.push(result);
+ }
+ });
+
+ // verify that the supported values can also be set through the locale
+ supportedValues.forEach(function (value) {
+ dateTimeFormat = new Intl.DateTimeFormat([defaultLocale + "-u-" + option.key + "-" + value]);
+ result = dateTimeFormat.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Property " + option.property + " couldn't be set through locale extension key " + option.key + ".");
+ });
+
+ // verify that the options setting overrides the locale setting
+ supportedValues.forEach(function (value) {
+ let otherValue;
+ option.values.forEach(function (possibleValue) {
+ if (possibleValue !== value) {
+ otherValue = possibleValue;
+ }
+ });
+ if (otherValue !== undefined) {
+ opt = {};
+ opt[option.property] = value;
+ dateTimeFormat = new Intl.DateTimeFormat([defaultLocale + "-u-" + option.key + "-" + otherValue], opt);
+ result = dateTimeFormat.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Options value for property " + option.property + " doesn't override locale extension key " + option.key + ".");
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prop-desc.js
new file mode 100644
index 0000000000..c2b774815d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat-intro
+description: >
+ "DateTimeFormat" property of Intl.
+info: |
+ Intl.DateTimeFormat (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, "DateTimeFormat", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/DateTimeFormat/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..118fadf545
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/proto-from-ctor-realm.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.DateTimeFormat ( [ locales [ , options ] ] )
+
+ 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
+ 2. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%DateTimeFormatPrototype%", « ... »).
+ ...
+ 6. Return dateTimeFormat.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var dtf;
+
+newTarget.prototype = undefined;
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = false;
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 1;
+dtf = Reflect.construct(Intl.DateTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(dtf), other.Intl.DateTimeFormat.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/builtin.js
new file mode 100644
index 0000000000..dd849faaef
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/builtin.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.3_L15
+description: >
+ Tests that Intl.DateTimeFormat.prototype meets the requirements
+ for built-in objects defined by the introduction of chapter 17 of
+ the ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert(Object.isExtensible(Intl.DateTimeFormat.prototype), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.DateTimeFormat.prototype), Object.prototype,
+ "Built-in prototype objects must have Object.prototype as their prototype.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..6d19563ee2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.constructor
+description: >
+ "constructor" property of Intl.DateTimeFormat.prototype.
+info: |
+ Intl.DateTimeFormat.prototype.constructor
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.prototype, "constructor", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/value.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/value.js
new file mode 100644
index 0000000000..5092db0768
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/constructor/value.js
@@ -0,0 +1,14 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3.1
+description: >
+ Tests that Intl.DateTimeFormat.prototype.constructor is the
+ Intl.DateTimeFormat.
+author: Roozbeh Pournader
+---*/
+
+assert.sameValue(Intl.DateTimeFormat.prototype.constructor, Intl.DateTimeFormat, "Intl.DateTimeFormat.prototype.constructor is not the same as Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/bound-to-datetimeformat-instance.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/bound-to-datetimeformat-instance.js
new file mode 100644
index 0000000000..485a7c0720
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/bound-to-datetimeformat-instance.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_1_c
+description: Tests that format function is bound to its Intl.DateTimeFormat.
+author: Norbert Lindenberg
+---*/
+
+var dates = [new Date(), new Date(0), new Date(Date.parse("1989-11-09T17:57:00Z"))];
+var locales = [undefined, ["de"], ["th-u-ca-gregory-nu-thai"], ["en"], ["ja-u-ca-japanese"], ["ar-u-ca-islamicc-nu-arab"]];
+var options = [
+ undefined,
+ {hour12: false},
+ {month: "long", day: "numeric", hour: "2-digit", minute: "2-digit"}
+];
+
+locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var formatObj = new Intl.DateTimeFormat(locales, options);
+ var formatFunc = formatObj.format;
+ dates.forEach(function (date) {
+ var referenceFormatted = formatObj.format(date);
+ var formatted = formatFunc(date);
+ assert.sameValue(referenceFormatted, formatted, "format function produces different result than format method for locales " + locales + "; options: " + (options ? JSON.stringify(options) : options) + ".");
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/builtin.js
new file mode 100644
index 0000000000..c4870b0b57
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/builtin.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_L15
+description: >
+ Tests that the getter for Intl.DateTimeFormat.prototype.format
+ meets the requirements for built-in objects defined by the
+ introduction of chapter 17 of the ECMAScript Language
+ Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var formatFn = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get;
+
+assert.sameValue(Object.prototype.toString.call(formatFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatFn), Function.prototype);
+
+assert.sameValue(formatFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/date-constructor-not-called.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/date-constructor-not-called.js
new file mode 100644
index 0000000000..f13f9425ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/date-constructor-not-called.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ The Date constructor is not called to convert the input value.
+info: >
+ 12.1.5 DateTime Format Functions
+
+ ...
+ 3. If date is not provided or is undefined, then
+ ...
+ 4. Else,
+ a. Let x be ? ToNumber(date).
+ 5. Return FormatDateTime(dtf, x).
+
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. ...
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var dateTimeString = "2017-11-10T14:09:00.000Z";
+
+// |dateTimeString| is valid ISO-8601 style date/time string.
+assert.notSameValue(new Date(dateTimeString), NaN);
+
+// Ensure string input values are not converted to time values by calling the
+// Date constructor.
+assert.throws(RangeError, function() {
+ dtf.format(dateTimeString);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-long-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-long-en.js
new file mode 100644
index 0000000000..13b8f374b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-long-en.js
@@ -0,0 +1,95 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of dayPeriod, long format.
+features: [Intl.DateTimeFormat-dayPeriod]
+locale: [en-US]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const long = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'long'
+});
+
+assert.sameValue(long.format(d0000), 'at night', '00:00, long format');
+assert.sameValue(long.format(d0100), 'at night', '01:00, long format');
+assert.sameValue(long.format(d0200), 'at night', '02:00, long format');
+assert.sameValue(long.format(d0300), 'at night', '03:00, long format');
+assert.sameValue(long.format(d0400), 'at night', '04:00, long format');
+assert.sameValue(long.format(d0500), 'at night', '05:00, long format');
+assert.sameValue(long.format(d0600), 'in the morning', '06:00, long format');
+assert.sameValue(long.format(d0700), 'in the morning', '07:00, long format');
+assert.sameValue(long.format(d0800), 'in the morning', '08:00, long format');
+assert.sameValue(long.format(d0900), 'in the morning', '09:00, long format');
+assert.sameValue(long.format(d1000), 'in the morning', '10:00, long format');
+assert.sameValue(long.format(d1100), 'in the morning', '11:00, long format');
+assert.sameValue(long.format(d1200), 'noon', '12:00, long format');
+assert.sameValue(long.format(d1300), 'in the afternoon', '13:00, long format');
+assert.sameValue(long.format(d1400), 'in the afternoon', '14:00, long format');
+assert.sameValue(long.format(d1500), 'in the afternoon', '15:00, long format');
+assert.sameValue(long.format(d1600), 'in the afternoon', '16:00, long format');
+assert.sameValue(long.format(d1700), 'in the afternoon', '17:00, long format');
+assert.sameValue(long.format(d1800), 'in the evening', '18:00, long format');
+assert.sameValue(long.format(d1900), 'in the evening', '19:00, long format');
+assert.sameValue(long.format(d2000), 'in the evening', '20:00, long format');
+assert.sameValue(long.format(d2100), 'at night', '21:00, long format');
+assert.sameValue(long.format(d2200), 'at night', '22:00, long format');
+assert.sameValue(long.format(d2300), 'at night', '23:00, long format');
+
+const longNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'long',
+ hour: 'numeric'
+});
+
+assert.sameValue(longNumeric.format(d0000), '12 at night', '00:00, long-numeric');
+assert.sameValue(longNumeric.format(d0100), '1 at night', '01:00, long-numeric');
+assert.sameValue(longNumeric.format(d0200), '2 at night', '02:00, long-numeric');
+assert.sameValue(longNumeric.format(d0300), '3 at night', '03:00, long-numeric');
+assert.sameValue(longNumeric.format(d0400), '4 at night', '04:00, long-numeric');
+assert.sameValue(longNumeric.format(d0500), '5 at night', '05:00, long-numeric');
+assert.sameValue(longNumeric.format(d0600), '6 in the morning', '06:00, long-numeric');
+assert.sameValue(longNumeric.format(d0700), '7 in the morning', '07:00, long-numeric');
+assert.sameValue(longNumeric.format(d0800), '8 in the morning', '08:00, long-numeric');
+assert.sameValue(longNumeric.format(d0900), '9 in the morning', '09:00, long-numeric');
+assert.sameValue(longNumeric.format(d1000), '10 in the morning', '10:00, long-numeric');
+assert.sameValue(longNumeric.format(d1100), '11 in the morning', '11:00, long-numeric');
+assert.sameValue(longNumeric.format(d1200), '12 noon', '12:00, long-numeric');
+assert.sameValue(longNumeric.format(d1300), '1 in the afternoon', '13:00, long-numeric');
+assert.sameValue(longNumeric.format(d1400), '2 in the afternoon', '14:00, long-numeric');
+assert.sameValue(longNumeric.format(d1500), '3 in the afternoon', '15:00, long-numeric');
+assert.sameValue(longNumeric.format(d1600), '4 in the afternoon', '16:00, long-numeric');
+assert.sameValue(longNumeric.format(d1700), '5 in the afternoon', '17:00, long-numeric');
+assert.sameValue(longNumeric.format(d1800), '6 in the evening', '18:00, long-numeric');
+assert.sameValue(longNumeric.format(d1900), '7 in the evening', '19:00, long-numeric');
+assert.sameValue(longNumeric.format(d2000), '8 in the evening', '20:00, long-numeric');
+assert.sameValue(longNumeric.format(d2100), '9 at night', '21:00, long-numeric');
+assert.sameValue(longNumeric.format(d2200), '10 at night', '22:00, long-numeric');
+assert.sameValue(longNumeric.format(d2300), '11 at night', '23:00, long-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-narrow-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-narrow-en.js
new file mode 100644
index 0000000000..4d26a5131d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-narrow-en.js
@@ -0,0 +1,95 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of dayPeriod, narrow format.
+features: [Intl.DateTimeFormat-dayPeriod]
+locale: [en-US]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const narrow = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'narrow'
+});
+
+assert.sameValue(narrow.format(d0000), 'at night', '00:00, narrow format');
+assert.sameValue(narrow.format(d0100), 'at night', '01:00, narrow format');
+assert.sameValue(narrow.format(d0200), 'at night', '02:00, narrow format');
+assert.sameValue(narrow.format(d0300), 'at night', '03:00, narrow format');
+assert.sameValue(narrow.format(d0400), 'at night', '04:00, narrow format');
+assert.sameValue(narrow.format(d0500), 'at night', '05:00, narrow format');
+assert.sameValue(narrow.format(d0600), 'in the morning', '06:00, narrow format');
+assert.sameValue(narrow.format(d0700), 'in the morning', '07:00, narrow format');
+assert.sameValue(narrow.format(d0800), 'in the morning', '08:00, narrow format');
+assert.sameValue(narrow.format(d0900), 'in the morning', '09:00, narrow format');
+assert.sameValue(narrow.format(d1000), 'in the morning', '10:00, narrow format');
+assert.sameValue(narrow.format(d1100), 'in the morning', '11:00, narrow format');
+assert.sameValue(narrow.format(d1200), 'n', '12:00, narrow format');
+assert.sameValue(narrow.format(d1300), 'in the afternoon', '13:00, narrow format');
+assert.sameValue(narrow.format(d1400), 'in the afternoon', '14:00, narrow format');
+assert.sameValue(narrow.format(d1500), 'in the afternoon', '15:00, narrow format');
+assert.sameValue(narrow.format(d1600), 'in the afternoon', '16:00, narrow format');
+assert.sameValue(narrow.format(d1700), 'in the afternoon', '17:00, narrow format');
+assert.sameValue(narrow.format(d1800), 'in the evening', '18:00, narrow format');
+assert.sameValue(narrow.format(d1900), 'in the evening', '19:00, narrow format');
+assert.sameValue(narrow.format(d2000), 'in the evening', '20:00, narrow format');
+assert.sameValue(narrow.format(d2100), 'at night', '21:00, narrow format');
+assert.sameValue(narrow.format(d2200), 'at night', '22:00, narrow format');
+assert.sameValue(narrow.format(d2300), 'at night', '23:00, narrow format');
+
+const narrowNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'narrow',
+ hour: 'numeric'
+});
+
+assert.sameValue(narrowNumeric.format(d0000), '12 at night', '00:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0100), '1 at night', '01:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0200), '2 at night', '02:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0300), '3 at night', '03:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0400), '4 at night', '04:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0500), '5 at night', '05:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0600), '6 in the morning', '06:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0700), '7 in the morning', '07:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0800), '8 in the morning', '08:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d0900), '9 in the morning', '09:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1000), '10 in the morning', '10:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1100), '11 in the morning', '11:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1200), '12 n', '12:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1300), '1 in the afternoon', '13:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1400), '2 in the afternoon', '14:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1500), '3 in the afternoon', '15:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1600), '4 in the afternoon', '16:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1700), '5 in the afternoon', '17:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1800), '6 in the evening', '18:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d1900), '7 in the evening', '19:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d2000), '8 in the evening', '20:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d2100), '9 at night', '21:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d2200), '10 at night', '22:00, narrow-numeric');
+assert.sameValue(narrowNumeric.format(d2300), '11 at night', '23:00, narrow-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-short-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-short-en.js
new file mode 100644
index 0000000000..f7b410f24d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/dayPeriod-short-en.js
@@ -0,0 +1,95 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializedatetimeformat
+description: Checks basic handling of dayPeriod, short format.
+features: [Intl.DateTimeFormat-dayPeriod]
+locale: [en-US]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const short = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'short'
+});
+
+assert.sameValue(short.format(d0000), 'at night', '00:00, short format');
+assert.sameValue(short.format(d0100), 'at night', '01:00, short format');
+assert.sameValue(short.format(d0200), 'at night', '02:00, short format');
+assert.sameValue(short.format(d0300), 'at night', '03:00, short format');
+assert.sameValue(short.format(d0400), 'at night', '04:00, short format');
+assert.sameValue(short.format(d0500), 'at night', '05:00, short format');
+assert.sameValue(short.format(d0600), 'in the morning', '06:00, short format');
+assert.sameValue(short.format(d0700), 'in the morning', '07:00, short format');
+assert.sameValue(short.format(d0800), 'in the morning', '08:00, short format');
+assert.sameValue(short.format(d0900), 'in the morning', '09:00, short format');
+assert.sameValue(short.format(d1000), 'in the morning', '10:00, short format');
+assert.sameValue(short.format(d1100), 'in the morning', '11:00, short format');
+assert.sameValue(short.format(d1200), 'noon', '12:00, short format');
+assert.sameValue(short.format(d1300), 'in the afternoon', '13:00, short format');
+assert.sameValue(short.format(d1400), 'in the afternoon', '14:00, short format');
+assert.sameValue(short.format(d1500), 'in the afternoon', '15:00, short format');
+assert.sameValue(short.format(d1600), 'in the afternoon', '16:00, short format');
+assert.sameValue(short.format(d1700), 'in the afternoon', '17:00, short format');
+assert.sameValue(short.format(d1800), 'in the evening', '18:00, short format');
+assert.sameValue(short.format(d1900), 'in the evening', '19:00, short format');
+assert.sameValue(short.format(d2000), 'in the evening', '20:00, short format');
+assert.sameValue(short.format(d2100), 'at night', '21:00, short format');
+assert.sameValue(short.format(d2200), 'at night', '22:00, short format');
+assert.sameValue(short.format(d2300), 'at night', '23:00, short format');
+
+const shortNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'short',
+ hour: 'numeric'
+});
+
+assert.sameValue(shortNumeric.format(d0000), '12 at night', '00:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0100), '1 at night', '01:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0200), '2 at night', '02:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0300), '3 at night', '03:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0400), '4 at night', '04:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0500), '5 at night', '05:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0600), '6 in the morning', '06:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0700), '7 in the morning', '07:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0800), '8 in the morning', '08:00, short-numeric');
+assert.sameValue(shortNumeric.format(d0900), '9 in the morning', '09:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1000), '10 in the morning', '10:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1100), '11 in the morning', '11:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1200), '12 noon', '12:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1300), '1 in the afternoon', '13:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1400), '2 in the afternoon', '14:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1500), '3 in the afternoon', '15:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1600), '4 in the afternoon', '16:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1700), '5 in the afternoon', '17:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1800), '6 in the evening', '18:00, short-numeric');
+assert.sameValue(shortNumeric.format(d1900), '7 in the evening', '19:00, short-numeric');
+assert.sameValue(shortNumeric.format(d2000), '8 in the evening', '20:00, short-numeric');
+assert.sameValue(shortNumeric.format(d2100), '9 at night', '21:00, short-numeric');
+assert.sameValue(shortNumeric.format(d2200), '10 at night', '22:00, short-numeric');
+assert.sameValue(shortNumeric.format(d2300), '11 at night', '23:00, short-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-builtin.js
new file mode 100644
index 0000000000..d5270e354a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-builtin.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_1_a_L15
+description: >
+ Tests that the function returned by
+ Intl.DateTimeFormat.prototype.format meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var formatFn = new Intl.DateTimeFormat().format;
+
+assert.sameValue(Object.prototype.toString.call(formatFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatFn), Function.prototype);
+
+assert.sameValue(formatFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-length.js
new file mode 100644
index 0000000000..255417c98a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-length.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.format
+description: >
+ The length of the bound DateTime Format function is 1.
+info: |
+ get Intl.DateTimeFormat.prototype.format
+
+ ...
+ 4. If dtf.[[BoundFormat]] is undefined, then
+ a. Let F be a new built-in function object as defined in DateTime Format Functions (12.1.5).
+ b. Let bf be BoundFunctionCreate(F, dft, « »).
+ c. Perform ! DefinePropertyOrThrow(bf, "length", PropertyDescriptor {[[Value]]: 1,
+ [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
+ ...
+
+includes: [propertyHelper.js]
+---*/
+
+var formatFn = new Intl.DateTimeFormat().format;
+
+verifyProperty(formatFn, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-name.js
new file mode 100644
index 0000000000..73922bca5f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-name.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.format
+description: >
+ The bound DateTimeFormat format function is an anonymous function.
+info: |
+ 12.4.3 get Intl.DateTimeFormat.prototype.compare
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in function object, including constructors, has a `name`
+ property whose value is a String. Functions that are identified as
+ anonymous functions use the empty string as the value of the `name`
+ property.
+ Unless otherwise specified, the `name` property of a built-in function
+ object has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*,
+ [[Configurable]]: *true* }.
+includes: [propertyHelper.js]
+---*/
+
+var formatFn = new Intl.DateTimeFormat().format;
+
+verifyProperty(formatFn, "name", {
+ value: "", writable: false, enumerable: false, configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-property-order.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-property-order.js
new file mode 100644
index 0000000000..78fcf6950b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/format-function-property-order.js
@@ -0,0 +1,18 @@
+// Copyright (C) 2020 ExE Boss. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createbuiltinfunction
+description: DateTimeFormat bound format function property order
+info: |
+ Set order: "length", "name"
+includes: [compareArray.js]
+---*/
+
+var formatFn = new Intl.DateTimeFormat().format;
+
+assert.compareArray(
+ Object.getOwnPropertyNames(formatFn),
+ ['length', 'name']
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/fractionalSecondDigits.js
new file mode 100644
index 0000000000..25af5ada6d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/fractionalSecondDigits.js
@@ -0,0 +1,34 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of fractionalSecondDigits.
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+locale: [en-US]
+---*/
+
+const d1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+const d2 = new Date(2019, 7, 10, 1, 2, 3, 567);
+
+let dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: undefined});
+assert.sameValue(dtf.format(d1), "02:03", "no fractionalSecondDigits");
+assert.sameValue(dtf.format(d2), "02:03", "no fractionalSecondDigits");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 1});
+assert.sameValue(dtf.format(d1), "02:03.2", "1 fractionalSecondDigits round down");
+assert.sameValue(dtf.format(d2), "02:03.5", "1 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 2});
+assert.sameValue(dtf.format(d1), "02:03.23", "2 fractionalSecondDigits round down");
+assert.sameValue(dtf.format(d2), "02:03.56", "2 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 3});
+assert.sameValue(dtf.format(d1), "02:03.234", "3 fractionalSecondDigits round down");
+assert.sameValue(dtf.format(d2), "02:03.567", "3 fractionalSecondDigits round down");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/length.js
new file mode 100644
index 0000000000..fe1ec0b080
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/length.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.format
+description: >
+ get Intl.DateTimeFormat.prototype.format.length is 0.
+info: |
+ get Intl.DateTimeFormat.prototype.format
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format");
+
+verifyProperty(desc.get, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/name.js
new file mode 100644
index 0000000000..3f7419112f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/name.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.format
+description: >
+ get Intl.DateTimeFormat.prototype.format.name is "get format".
+info: |
+ 12.4.3 get Intl.DateTimeFormat.prototype.format
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format");
+
+verifyProperty(desc.get, "name", {
+ value: "get format",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/no-instanceof.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/no-instanceof.js
new file mode 100644
index 0000000000..010d55def0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/no-instanceof.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.format
+description: >
+ Tests that Intl.DateTimeFormat.prototype.format calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ UnwrapDateTimeFormat ( dtf )
+ 2. If dtf does not have an [[InitializedDateTimeFormat]] internal slot and
+ ? OrdinaryHasInstance(%DateTimeFormat%, dtf) is true, then
+ a. Return ? Get(dtf, %Intl%.[[FallbackSymbol]]).
+---*/
+
+const dtf = Object.create(Intl.DateTimeFormat.prototype);
+
+Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+assert.throws(TypeError, () => dtf.format);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js
new file mode 100644
index 0000000000..2f03dcae06
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js
@@ -0,0 +1,29 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that date and time formatting in an offset time zone
+ matches that in the equivalent Etc/GMT±n time zone.
+---*/
+let offsetTimeZones = {
+ '+0300': 'Etc/GMT-3',
+ '+1400': 'Etc/GMT-14',
+ '+02': 'Etc/GMT-2',
+ '+13:00': 'Etc/GMT-13',
+ '-07:00': 'Etc/GMT+7',
+ '-12': 'Etc/GMT+12',
+ '−0900': 'Etc/GMT+9',
+ '−10:00': 'Etc/GMT+10',
+ '−0500': 'Etc/GMT+5',
+};
+let date = new Date('1995-12-17T03:24:56Z');
+Object.entries(offsetTimeZones).forEach(([offsetZone, gmtZone]) => {
+ let offsetDf = new Intl.DateTimeFormat("en",
+ {timeZone: offsetZone, dateStyle: "short", timeStyle: "short"});
+ let gmtDf = new Intl.DateTimeFormat("en",
+ {timeZone: gmtZone, dateStyle: "short", timeStyle: "short"});
+ assert.sameValue(offsetDf.format(date), gmtDf.format(date), `${offsetZone} vs. ${gmtZone}:`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/proleptic-gregorian-calendar.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/proleptic-gregorian-calendar.js
new file mode 100644
index 0000000000..c7f826ddfb
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/proleptic-gregorian-calendar.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_FDT_7_a_iv
+description: >
+ Tests that format uses a proleptic Gregorian calendar with no year
+ 0.
+author: Norbert Lindenberg
+---*/
+
+var dates = [
+ 0, // January 1, 1970
+ -62151602400000, // in June 1 BC
+ -8640000000000000 // beginning of ECMAScript time
+];
+
+var format = new Intl.DateTimeFormat(["en-US"], {year: "numeric", era: "short", timeZone: "UTC"});
+
+// this test requires a Gregorian calendar, which we usually find in the US
+assert.sameValue(format.resolvedOptions().calendar, "gregory", "Internal error: Didn't find Gregorian calendar");
+
+dates.forEach(function (date) {
+ var year = new Date(date).getUTCFullYear();
+ var expectedYear = year <= 0 ? 1 - year : year;
+ var expectedYearString = expectedYear.toLocaleString(["en-US"], {useGrouping: false});
+ var expectedEra = year <= 0 ? /BC/ : /AD|(?:^|[^B])CE/;
+ var dateString = format.format(date);
+ assert.notSameValue(dateString.indexOf(expectedYearString), -1, "Formatted year doesn't contain expected year – expected " + expectedYearString + ", got " + dateString + ".");
+ assert(expectedEra.test(dateString), "Formatted year doesn't contain expected era – expected " + expectedEra + ", got " + dateString + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/prop-desc.js
new file mode 100644
index 0000000000..55970d90a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/prop-desc.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.format
+description: >
+ "format" property of Intl.DateTimeFormat.prototype.
+info: |
+ get Intl.DateTimeFormat.prototype.format
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+ If only a get accessor function is described, the set accessor function is the default
+ value, undefined. If only a set accessor is described the get accessor is the default
+ value, undefined.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format");
+
+assert.sameValue(desc.set, undefined);
+assert.sameValue(typeof desc.get, "function");
+
+verifyProperty(Intl.DateTimeFormat.prototype, "format", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/related-year-zh.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/related-year-zh.js
new file mode 100644
index 0000000000..46be36f3c1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/related-year-zh.js
@@ -0,0 +1,20 @@
+// Copyright 2019 Google Inc, Igalia S.L. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: >
+ Checks the output of 'relatedYear' and 'yearName' type, and
+ the choice of pattern based on calendar.
+locale: [zh-u-ca-chinese]
+features: [Array.prototype.includes]
+---*/
+
+const df = new Intl.DateTimeFormat("zh-u-ca-chinese", {year: "numeric"});
+const date = new Date(2019, 5, 1);
+const formatted = df.format(date);
+const expected = ["2019己亥年", "己亥年"];
+assert(expected.includes(formatted));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/shell.js
new file mode 100644
index 0000000000..985f46c993
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/shell.js
@@ -0,0 +1,55 @@
+// GENERATED, DO NOT EDIT
+// file: dateConstants.js
+// Copyright (C) 2009 the Sputnik authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Collection of date-centric values
+defines:
+ - date_1899_end
+ - date_1900_start
+ - date_1969_end
+ - date_1970_start
+ - date_1999_end
+ - date_2000_start
+ - date_2099_end
+ - date_2100_start
+ - start_of_time
+ - end_of_time
+---*/
+
+var date_1899_end = -2208988800001;
+var date_1900_start = -2208988800000;
+var date_1969_end = -1;
+var date_1970_start = 0;
+var date_1999_end = 946684799999;
+var date_2000_start = 946684800000;
+var date_2099_end = 4102444799999;
+var date_2100_start = 4102444800000;
+
+var start_of_time = -8.64e15;
+var end_of_time = 8.64e15;
+
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/taint-Object-prototype.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/taint-Object-prototype.js
new file mode 100644
index 0000000000..b4ca8b4b0d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/taint-Object-prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_TLT_2
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["weekday", "era", "year", "month", "day", "hour", "minute", "second", "inDST"]);
+
+var format = new Intl.DateTimeFormat();
+var time = format.format();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js
new file mode 100644
index 0000000000..e9af1ca9c8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js
@@ -0,0 +1,52 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-datetime-format-functions
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDayPeriodSpace =
+ new Intl.DateTimeFormat("en-US", { timeStyle: "short" })
+ .formatToParts(0)
+ .find((part, i, parts) => part.type === "literal" && parts[i + 1].type === "dayPeriod")?.value || "";
+
+const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "Pacific/Apia" });
+
+const date = new Temporal.PlainDate(2021, 8, 4);
+const dateResult = formatter.format(date);
+assert.sameValue(dateResult, "8/4/2021", "plain date");
+
+const datetime1 = new Temporal.PlainDateTime(2021, 8, 4, 0, 30, 45, 123, 456, 789);
+const datetimeResult1 = formatter.format(datetime1);
+assert.sameValue(
+ datetimeResult1,
+ `8/4/2021, 12:30:45${usDayPeriodSpace}AM`,
+ "plain datetime close to beginning of day"
+);
+const datetime2 = new Temporal.PlainDateTime(2021, 8, 4, 23, 30, 45, 123, 456, 789);
+const datetimeResult2 = formatter.format(datetime2);
+assert.sameValue(datetimeResult2, `8/4/2021, 11:30:45${usDayPeriodSpace}PM`, "plain datetime close to end of day");
+
+const monthDay = new Temporal.PlainMonthDay(8, 4, "gregory");
+const monthDayResult = formatter.format(monthDay);
+assert.sameValue(monthDayResult, "8/4", "plain month-day");
+
+const time1 = new Temporal.PlainTime(0, 30, 45, 123, 456, 789);
+const timeResult1 = formatter.format(time1);
+assert.sameValue(timeResult1, `12:30:45${usDayPeriodSpace}AM`, "plain time close to beginning of day");
+const time2 = new Temporal.PlainTime(23, 30, 45, 123, 456, 789);
+const timeResult2 = formatter.format(time2);
+assert.sameValue(timeResult2, `11:30:45${usDayPeriodSpace}PM`, "plain time close to end of day");
+
+const month = new Temporal.PlainYearMonth(2021, 8, "gregory");
+const monthResult = formatter.format(month);
+assert.sameValue(monthResult, "8/2021", "plain year-month");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-zoneddatetime-not-supported.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-zoneddatetime-not-supported.js
new file mode 100644
index 0000000000..d36514f42b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/temporal-zoneddatetime-not-supported.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-datetime-format-functions
+description: Temporal.ZonedDateTime is not supported directly in format()
+features: [Temporal]
+---*/
+
+const formatter = new Intl.DateTimeFormat();
+
+// Check that TypeError would not be thrown for a different reason
+const {timeZone, ...options} = formatter.resolvedOptions();
+const datetime = new Temporal.ZonedDateTime(0n, timeZone);
+assert.sameValue(typeof datetime.toLocaleString(undefined, options), "string", "toLocaleString() with same options succeeds");
+
+assert.throws(TypeError, () => formatter.format(datetime), "format() does not support Temporal.ZonedDateTime");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/throws-value-non-finite.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/throws-value-non-finite.js
new file mode 100644
index 0000000000..598b9ec88e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/throws-value-non-finite.js
@@ -0,0 +1,20 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3.2_FDT_1
+description: Tests that format handles non-finite values correctly.
+author: Norbert Lindenberg
+---*/
+
+var invalidValues = [NaN, Infinity, -Infinity];
+
+var format = new Intl.DateTimeFormat();
+
+invalidValues.forEach(function (value) {
+ assert.throws(RangeError, function() {
+ var result = format.format(value);
+ }, "Invalid value " + value + " was not rejected.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-near-time-boundaries.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-near-time-boundaries.js
new file mode 100644
index 0000000000..0c415ec7ea
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-near-time-boundaries.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ TimeClip is applied when calling Intl.DateTimeFormat.prototype.format.
+info: >
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. ...
+
+ 20.3.1.15 TimeClip ( time )
+ ...
+ 2. If abs(time) > 8.64 × 10^15, return NaN.
+ ...
+
+includes: [dateConstants.js]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+// Test values near the start of the ECMAScript time range.
+assert.throws(RangeError, function() {
+ dtf.format(start_of_time - 1);
+});
+assert.sameValue(typeof dtf.format(start_of_time), "string");
+assert.sameValue(typeof dtf.format(start_of_time + 1), "string");
+
+// Test values near the end of the ECMAScript time range.
+assert.sameValue(typeof dtf.format(end_of_time - 1), "string");
+assert.sameValue(typeof dtf.format(end_of_time), "string");
+assert.throws(RangeError, function() {
+ dtf.format(end_of_time + 1);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-to-integer.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-to-integer.js
new file mode 100644
index 0000000000..d7fbe4db66
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/time-clip-to-integer.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ TimeClip applies ToInteger on its input value.
+info: >
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. ...
+
+ 20.3.1.15 TimeClip ( time )
+ ...
+ 3. Let clippedTime be ! ToInteger(time).
+ 4. If clippedTime is -0, set clippedTime to +0.
+ 5. Return clippedTime.
+---*/
+
+// Switch to a time format instead of using DateTimeFormat's default date-only format.
+var dtf = new Intl.DateTimeFormat(undefined, {
+ hour: "numeric", minute: "numeric", second: "numeric"
+});
+
+var expected = dtf.format(0);
+
+assert.sameValue(dtf.format(-0.9), expected, "format(-0.9)");
+assert.sameValue(dtf.format(-0.5), expected, "format(-0.5)");
+assert.sameValue(dtf.format(-0.1), expected, "format(-0.1)");
+assert.sameValue(dtf.format(-Number.MIN_VALUE), expected, "format(-Number.MIN_VALUE)");
+assert.sameValue(dtf.format(-0), expected, "format(-0)");
+assert.sameValue(dtf.format(+0), expected, "format(+0)");
+assert.sameValue(dtf.format(Number.MIN_VALUE), expected, "format(Number.MIN_VALUE)");
+assert.sameValue(dtf.format(0.1), expected, "format(0.1)");
+assert.sameValue(dtf.format(0.5), expected, "format(0.5)");
+assert.sameValue(dtf.format(0.9), expected, "format(0.9)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js
new file mode 100644
index 0000000000..36893b5f9a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js
@@ -0,0 +1,114 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-date-time-style-pattern
+description: Checks basic handling of timeStyle and dateStyle.
+features: [Intl.DateTimeFormat-datetimestyle, Array.prototype.includes]
+locale: [en-US]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDayPeriodSpace =
+ new Intl.DateTimeFormat("en-US", { timeStyle: "short" })
+ .formatToParts(0)
+ .find((part, i, parts) => part.type === "literal" && parts[i + 1].type === "dayPeriod")?.value || "";
+
+const date = new Date("1886-05-01T14:12:47Z");
+const dateOptions = [
+ ["full", "Saturday, May 1, 1886"],
+ ["long", "May 1, 1886"],
+ ["medium", "May 1, 1886"],
+ ["short", "5/1/86"],
+];
+
+const timeOptions = [
+ ["full", `2:12:47${usDayPeriodSpace}PM Coordinated Universal Time`, "14:12:47 Coordinated Universal Time"],
+ ["long", `2:12:47${usDayPeriodSpace}PM UTC`, "14:12:47 UTC"],
+ ["medium", `2:12:47${usDayPeriodSpace}PM`, "14:12:47"],
+ ["short", `2:12${usDayPeriodSpace}PM`, "14:12"],
+];
+
+const options12 = [
+ { "hour12": true },
+ { "hourCycle": "h11" },
+ { "hourCycle": "h12" },
+ { "hourCycle": "h23", "hour12": true },
+ { "hourCycle": "h24", "hour12": true },
+];
+
+const options24 = [
+ { "hour12": false },
+ { "hourCycle": "h23" },
+ { "hourCycle": "h24" },
+ { "hourCycle": "h11", "hour12": false },
+ { "hourCycle": "h12", "hour12": false },
+];
+
+for (const [dateStyle, expected] of dateOptions) {
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ dateStyle,
+ timeZone: "UTC",
+ });
+
+ const dateString = dtf.format(date);
+ assert.sameValue(dateString, expected, `Result for ${dateStyle}`);
+}
+
+for (const [timeStyle, expected12, expected24] of timeOptions) {
+ const check = (locale, options, expected) => {
+ const dtf = new Intl.DateTimeFormat(locale, {
+ timeStyle,
+ timeZone: "UTC",
+ ...options
+ });
+
+ const dateString = dtf.format(date);
+ assert.sameValue(dateString, expected, `Result for ${timeStyle} with ${JSON.stringify(options)}`);
+ };
+
+ check("en-US", {}, expected12);
+ check("en-US-u-hc-h11", {}, expected12);
+ check("en-US-u-hc-h12", {}, expected12);
+ check("en-US-u-hc-h23", {}, expected24);
+ check("en-US-u-hc-h24", {}, expected24);
+
+ for (const hourOptions of options12) {
+ check("en-US", hourOptions, expected12);
+ check("en-US-u-hc-h11", hourOptions, expected12);
+ check("en-US-u-hc-h12", hourOptions, expected12);
+ check("en-US-u-hc-h23", hourOptions, expected12);
+ check("en-US-u-hc-h24", hourOptions, expected12);
+ }
+
+ for (const hourOptions of options24) {
+ check("en-US", hourOptions, expected24);
+ check("en-US-u-hc-h11", hourOptions, expected24);
+ check("en-US-u-hc-h12", hourOptions, expected24);
+ check("en-US-u-hc-h23", hourOptions, expected24);
+ check("en-US-u-hc-h24", hourOptions, expected24);
+ }
+}
+
+for (const [dateStyle, expectedDate] of dateOptions) {
+ for (const [timeStyle, expectedTime] of timeOptions) {
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ dateStyle,
+ timeStyle,
+ timeZone: "UTC",
+ });
+ const result1 = [expectedDate, ", ", expectedTime].join("");
+ const result2 = [expectedDate, " at ", expectedTime].join("");
+
+ const dateString = dtf.format(date);
+ assert.sameValue(
+ [result1, result2].includes(dateString),
+ true,
+ `Result for date=${dateStyle} and time=${timeStyle}`
+ );
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-date-string.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-date-string.js
new file mode 100644
index 0000000000..ee452b5358
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-date-string.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ The Date constructor is not called to convert the input value.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const dtf = new Intl.DateTimeFormat();
+const dateTimeString = "2017-11-10T14:09:00.000Z";
+const date = new Date(dateTimeString);
+// |dateTimeString| is valid ISO-8601 style date/time string.
+assert.notSameValue(date, NaN);
+
+// ToNumber() will try to parse the string as an integer and yield NaN, rather
+// than attempting to parse it like the Date constructor would.
+assert.throws(RangeError, function() {
+ dtf.formatRange(dateTimeString, date);
+});
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, dateTimeString);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-near-time-boundaries.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-near-time-boundaries.js
new file mode 100644
index 0000000000..c6efcad3bf
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-near-time-boundaries.js
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ TimeClip is applied when calling Intl.DateTimeFormat.prototype.formatRange.
+info: |
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+ TimeClip ( time )
+ ...
+ 2. If abs(time) > 8.64 × 10^15, return NaN.
+ ...
+
+includes: [dateConstants.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const dtf = new Intl.DateTimeFormat();
+const date = Date.now();
+
+// Test values near the start of the ECMAScript time range.
+assert.throws(RangeError, function() {
+ dtf.formatRange(start_of_time - 1, date);
+});
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, start_of_time - 1);
+});
+assert.sameValue(typeof dtf.formatRange(start_of_time, date), "string");
+assert.sameValue(typeof dtf.formatRange(start_of_time + 1, date), "string");
+
+// Test values near the end of the ECMAScript time range.
+assert.sameValue(typeof dtf.formatRange(date, end_of_time - 1), "string");
+assert.sameValue(typeof dtf.formatRange(date, end_of_time), "string");
+assert.throws(RangeError, function() {
+ dtf.formatRange(end_of_time + 1, date);
+});
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, end_of_time + 1);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-to-integer.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-to-integer.js
new file mode 100644
index 0000000000..b698b4b9a9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-to-integer.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ TimeClip applies ToInteger on its input value.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+
+ TimeClip ( time )
+ ...
+ 3. Let clippedTime be ! ToInteger(time).
+ 4. If clippedTime is -0, set clippedTime to +0.
+ 5. Return clippedTime.
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+// Switch to a time format instead of using DateTimeFormat's default date-only format.
+const dtf = new Intl.DateTimeFormat(undefined, {
+ hour: "numeric", minute: "numeric", second: "numeric"
+});
+const date = Date.now();
+const expected = dtf.formatRange(0, date);
+
+assert.sameValue(dtf.formatRange(-0.9, date), expected, "formatRange(-0.9)");
+assert.sameValue(dtf.formatRange(-0.5, date), expected, "formatRange(-0.5)");
+assert.sameValue(dtf.formatRange(-0.1, date), expected, "formatRange(-0.1)");
+assert.sameValue(dtf.formatRange(-Number.MIN_VALUE, date), expected, "formatRange(-Number.MIN_VALUE)");
+assert.sameValue(dtf.formatRange(-0, date), expected, "formatRange(-0)");
+assert.sameValue(dtf.formatRange(+0, date), expected, "formatRange(+0)");
+assert.sameValue(dtf.formatRange(Number.MIN_VALUE, date), expected, "formatRange(Number.MIN_VALUE)");
+assert.sameValue(dtf.formatRange(0.1, date), expected, "formatRange(0.1)");
+assert.sameValue(dtf.formatRange(0.5, date), expected, "formatRange(0.5)");
+assert.sameValue(dtf.formatRange(0.9, date), expected, "formatRange(0.9)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-tonumber-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-tonumber-throws.js
new file mode 100644
index 0000000000..ddbe0ed2fe
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/argument-tonumber-throws.js
@@ -0,0 +1,56 @@
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Return abrupt completions from ToNumber(date)
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+features: [Symbol,Intl.DateTimeFormat-formatRange]
+---*/
+
+const date = Date.now();
+
+const objectValueOf = {
+ valueOf: function() {
+ throw new Test262Error();
+ }
+};
+
+const objectToString = {
+ toString: function() {
+ throw new Test262Error();
+ }
+};
+
+const dtf = new Intl.DateTimeFormat(["pt-BR"]);
+
+assert.throws(Test262Error, function() {
+ dtf.formatRange(objectValueOf, date);
+}, "valueOf start");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRange(date, objectValueOf);
+}, "valueOf end");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRange(objectToString, date);
+}, "toString start");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRange(date, objectToString);
+}, "toString end");
+
+const s = Symbol('1');
+assert.throws(TypeError, function() {
+ dtf.formatRange(s, date);
+}, "symbol start");
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(date, s);
+}, "symbol end");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/builtin.js
new file mode 100644
index 0000000000..d457d6a40b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/builtin.js
@@ -0,0 +1,32 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+ Tests that the Intl.DateTimeFormat.prototype.formatRange function meets the
+ requirements for built-in objects defined by the ECMAScript Language
+ Specification.
+includes: [isConstructor.js]
+features: [Reflect.construct,Intl.DateTimeFormat-formatRange]
+---*/
+
+const formatRange = Intl.DateTimeFormat.prototype.formatRange;
+
+assert.sameValue(Object.prototype.toString.call(formatRange), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatRange),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatRange), Function.prototype);
+
+assert.sameValue(formatRange.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatRange), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-infinity-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-infinity-throws.js
new file mode 100644
index 0000000000..d59691d42d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-infinity-throws.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to an Infinity value
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a RangeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 7. If x is greater than y, throw a RangeError exception.
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+
+ FormatDateTimeRange ( dateTimeFormat, x, y )
+
+ 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+ TimeClip ( time )
+ 1. If time is not finite, return NaN.
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var date = new Date();
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(Infinity, date);
+}, "+Infinity/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(-Infinity, date);
+}, "-Infinity/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, Infinity);
+}, "date/+Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, -Infinity);
+}, "date/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(Infinity, Infinity);
+}, "+Infinity/+Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(-Infinity, -Infinity);
+}, "-Infinity/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(Infinity, -Infinity);
+}, "+Infinity/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(-Infinity, Infinity);
+}, "-Infinity/+Infinity");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-nan-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-nan-throws.js
new file mode 100644
index 0000000000..25d90bafd6
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-is-nan-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to NaN
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a RangeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 7. If x is greater than y, throw a RangeError exception.
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+
+ FormatDateTimeRange ( dateTimeFormat, x, y )
+
+ 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var date = new Date();
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(NaN, date);
+}, "NaN/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(date, NaN);
+}, "date/NaN");
+
+assert.throws(RangeError, function() {
+ dtf.formatRange(NaN, NaN);
+}, "NaN/NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-same-returns-single-date.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-same-returns-single-date.js
new file mode 100644
index 0000000000..e2dab6cbc1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-same-returns-single-date.js
@@ -0,0 +1,63 @@
+// Copyright 2021 Google Inc. All rights reserved.
+// Copyright 2021 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ When startDate is equal to endDate, the output should be a string equal
+ to the output of Intl.DateTimeFormat.prototype.format.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 4. Let x be ? ToNumber(startDate).
+ 5. Let y be ? ToNumber(endDate).
+ 6. Return ? FormatDateTimeRange(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 13. If dateFieldsPracticallyEqual is true, then
+ a. Let pattern be dateTimeFormat.[[Pattern]].
+ b. Let patternParts be PartitionPattern(pattern).
+ c. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, tm1).
+ d. For each r in result do
+ i. Set r.[[Source]] to "shared".
+ e. Return result.
+
+features: [Intl.DateTimeFormat-formatRange]
+locale: [en-US]
+---*/
+
+{
+ const date = new Date(2019, 7, 10, 1, 2, 3, 234);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ assert.sameValue(dtf.formatRange(date, date), dtf.format(date), "same output with date options");
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ assert.sameValue(dtf.formatRange(date, date), dtf.format(date), "same output with time options");
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ assert.sameValue(dtf.formatRange(date, date), dtf.format(date), "same output with date-time options");
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ assert.sameValue(dtf.formatRange(date, date), dtf.format(date), "same output with dateStyle/timeStyle");
+}
+{
+ const date1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+ const date2 = new Date(2019, 7, 10, 1, 2, 3, 235);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ assert.sameValue(dtf.formatRange(date1, date2), dtf.format(date1), "same output with date options");
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ assert.sameValue(dtf.formatRange(date1, date2), dtf.format(date1), "same output with time options");
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ assert.sameValue(dtf.formatRange(date1, date2), dtf.format(date1), "same output with date-time options");
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ assert.sameValue(dtf.formatRange(date1, date2), dtf.format(date1), "same output with dateStyle/timeStyle");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-undefined-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-undefined-throws.js
new file mode 100644
index 0000000000..639d941101
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-undefined-throws.js
@@ -0,0 +1,44 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if startDate or endDate is undefined.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a TypeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(); // Not possible to poison this one
+}, "no args");
+
+var poison = { valueOf() { throw new Test262Error(); } };
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(undefined, poison);
+}, "date/undefined");
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(poison, undefined);
+}, "undefined/date");
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(poison);
+}, "only one arg");
+
+assert.throws(TypeError, function() {
+ dtf.formatRange(undefined, undefined);
+}, "undefined/undefined");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-x-greater-than-y-not-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-x-greater-than-y-not-throws.js
new file mode 100644
index 0000000000..1ddc9d49c7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/date-x-greater-than-y-not-throws.js
@@ -0,0 +1,35 @@
+// Copyright 2022 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Return a string if date x is greater than y.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 4. Let x be ? ToNumber(startDate).
+ 5. Let y be ? ToNumber(endDate).
+ 6. Return ? FormatDateTimeRange(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var x = new Date();
+var y = new Date();
+x.setDate(y.getDate() + 1);
+
+assert.sameValue("string", typeof dtf.formatRange(x, y));
+assert.sameValue("string", typeof dtf.formatRange(x, x));
+assert.sameValue("string", typeof dtf.formatRange(y, y));
+assert.sameValue("string", typeof dtf.formatRange(y, x));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/en-US.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/en-US.js
new file mode 100644
index 0000000000..756b9f8f35
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/en-US.js
@@ -0,0 +1,45 @@
+// Copyright (C) 2019 the V8 project authors, Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: Basic tests for the en-US output of formatRange()
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+locale: [en-US]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === "literal" && part.source === "shared").value;
+
+const date1 = new Date("2019-01-03T00:00:00");
+const date2 = new Date("2019-01-05T00:00:00");
+const date3 = new Date("2019-03-04T00:00:00");
+const date4 = new Date("2020-03-04T00:00:00");
+
+let dtf = new Intl.DateTimeFormat("en-US");
+assert.sameValue(dtf.formatRange(date1, date1), "1/3/2019");
+assert.sameValue(dtf.formatRange(date1, date2), `1/3/2019${usDateRangeSeparator}1/5/2019`);
+assert.sameValue(dtf.formatRange(date1, date3), `1/3/2019${usDateRangeSeparator}3/4/2019`);
+assert.sameValue(dtf.formatRange(date1, date4), `1/3/2019${usDateRangeSeparator}3/4/2020`);
+assert.sameValue(dtf.formatRange(date2, date3), `1/5/2019${usDateRangeSeparator}3/4/2019`);
+assert.sameValue(dtf.formatRange(date2, date4), `1/5/2019${usDateRangeSeparator}3/4/2020`);
+assert.sameValue(dtf.formatRange(date3, date4), `3/4/2019${usDateRangeSeparator}3/4/2020`);
+
+dtf = new Intl.DateTimeFormat("en-US", {year: "numeric", month: "short", day: "numeric"});
+assert.sameValue(dtf.formatRange(date1, date1), "Jan 3, 2019");
+assert.sameValue(dtf.formatRange(date1, date2), `Jan 3${usDateRangeSeparator}5, 2019`);
+assert.sameValue(dtf.formatRange(date1, date3), `Jan 3${usDateRangeSeparator}Mar 4, 2019`);
+assert.sameValue(dtf.formatRange(date1, date4), `Jan 3, 2019${usDateRangeSeparator}Mar 4, 2020`);
+assert.sameValue(dtf.formatRange(date2, date3), `Jan 5${usDateRangeSeparator}Mar 4, 2019`);
+assert.sameValue(dtf.formatRange(date2, date4), `Jan 5, 2019${usDateRangeSeparator}Mar 4, 2020`);
+assert.sameValue(dtf.formatRange(date3, date4), `Mar 4, 2019${usDateRangeSeparator}Mar 4, 2020`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/fractionalSecondDigits.js
new file mode 100644
index 0000000000..781ea43d26
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/fractionalSecondDigits.js
@@ -0,0 +1,42 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of fractionalSecondDigits.
+features: [Intl.DateTimeFormat-fractionalSecondDigits, Intl.DateTimeFormat-formatRange]
+locale: [en-US]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === "literal" && part.source === "shared").value;
+
+const d1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+const d2 = new Date(2019, 7, 10, 1, 2, 3, 567);
+const d3 = new Date(2019, 7, 10, 1, 2, 13, 987);
+
+let dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: undefined});
+assert.sameValue(dtf.formatRange(d1, d2), "02:03", "no fractionalSecondDigits");
+assert.sameValue(dtf.formatRange(d1, d3), `02:03${usDateRangeSeparator}02:13`, "no fractionalSecondDigits");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 1});
+assert.sameValue(dtf.formatRange(d1, d2), `02:03.2${usDateRangeSeparator}02:03.5`, "1 fractionalSecondDigits round down");
+assert.sameValue(dtf.formatRange(d1, d3), `02:03.2${usDateRangeSeparator}02:13.9`, "1 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 2});
+assert.sameValue(dtf.formatRange(d1, d2), `02:03.23${usDateRangeSeparator}02:03.56`, "2 fractionalSecondDigits round down");
+assert.sameValue(dtf.formatRange(d1, d3), `02:03.23${usDateRangeSeparator}02:13.98`, "2 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 3});
+assert.sameValue(dtf.formatRange(d1, d2), `02:03.234${usDateRangeSeparator}02:03.567`, "3 fractionalSecondDigits round down");
+assert.sameValue(dtf.formatRange(d1, d3), `02:03.234${usDateRangeSeparator}02:13.987`, "3 fractionalSecondDigits round down");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/length.js
new file mode 100644
index 0000000000..bd1605e0ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/length.js
@@ -0,0 +1,16 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatRange.length.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatRange, 'length', {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/name.js
new file mode 100644
index 0000000000..f3ded8535e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/name.js
@@ -0,0 +1,16 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatRange.name value and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatRange, 'name', {
+ value: 'formatRange',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/prop-desc.js
new file mode 100644
index 0000000000..e022b19320
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/prop-desc.js
@@ -0,0 +1,23 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Property type and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+assert.sameValue(
+ typeof Intl.DateTimeFormat.prototype.formatRange,
+ 'function',
+ '`typeof Intl.DateTimeFormat.prototype.formatRange` is `function`'
+);
+
+verifyProperty(Intl.DateTimeFormat.prototype, 'formatRange', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/shell.js
new file mode 100644
index 0000000000..985f46c993
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/shell.js
@@ -0,0 +1,55 @@
+// GENERATED, DO NOT EDIT
+// file: dateConstants.js
+// Copyright (C) 2009 the Sputnik authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Collection of date-centric values
+defines:
+ - date_1899_end
+ - date_1900_start
+ - date_1969_end
+ - date_1970_start
+ - date_1999_end
+ - date_2000_start
+ - date_2099_end
+ - date_2100_start
+ - start_of_time
+ - end_of_time
+---*/
+
+var date_1899_end = -2208988800001;
+var date_1900_start = -2208988800000;
+var date_1969_end = -1;
+var date_1970_start = 0;
+var date_1999_end = 946684799999;
+var date_2000_start = 946684800000;
+var date_2099_end = 4102444799999;
+var date_2100_start = 4102444800000;
+
+var start_of_time = -8.64e15;
+var end_of_time = 8.64e15;
+
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js
new file mode 100644
index 0000000000..a381435baa
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-objects-resolved-time-zone.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-datetime-format-functions
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal, Intl.DateTimeFormat-formatRange]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDayPeriodSpace =
+ new Intl.DateTimeFormat("en-US", { timeStyle: "short" })
+ .formatToParts(0)
+ .find((part, i, parts) => part.type === "literal" && parts[i + 1].type === "dayPeriod")?.value || "";
+const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === "literal" && part.source === "shared").value;
+
+const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "Pacific/Apia" });
+
+const date1 = new Temporal.PlainDate(2021, 8, 4);
+const date2 = new Temporal.PlainDate(2021, 8, 5);
+const dateResult = formatter.formatRange(date1, date2);
+assert.sameValue(dateResult, `8/4/2021${usDateRangeSeparator}8/5/2021`, "plain dates");
+
+const datetime1 = new Temporal.PlainDateTime(2021, 8, 4, 0, 30, 45, 123, 456, 789);
+const datetime2 = new Temporal.PlainDateTime(2021, 8, 4, 23, 30, 45, 123, 456, 789);
+const datetimeResult = formatter.formatRange(datetime1, datetime2);
+assert.sameValue(
+ datetimeResult,
+ `8/4/2021, 12:30:45${usDayPeriodSpace}AM${usDateRangeSeparator}11:30:45${usDayPeriodSpace}PM`,
+ "plain datetimes"
+);
+
+const monthDay1 = new Temporal.PlainMonthDay(8, 4, "gregory");
+const monthDay2 = new Temporal.PlainMonthDay(8, 5, "gregory");
+const monthDayResult = formatter.formatRange(monthDay1, monthDay2);
+assert.sameValue(monthDayResult, `8/4${usDateRangeSeparator}8/5`, "plain month-days");
+
+const time1 = new Temporal.PlainTime(0, 30, 45, 123, 456, 789);
+const time2 = new Temporal.PlainTime(23, 30, 45, 123, 456, 789);
+const timeResult = formatter.formatRange(time1, time2);
+assert.sameValue(
+ timeResult,
+ `12:30:45${usDayPeriodSpace}AM${usDateRangeSeparator}11:30:45${usDayPeriodSpace}PM`,
+ "plain times"
+);
+
+const month1 = new Temporal.PlainYearMonth(2021, 8, "gregory");
+const month2 = new Temporal.PlainYearMonth(2021, 9, "gregory");
+const monthResult = formatter.formatRange(month1, month2);
+assert.sameValue(monthResult, `8/2021${usDateRangeSeparator}9/2021`, "plain year-months");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-zoneddatetime-not-supported.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-zoneddatetime-not-supported.js
new file mode 100644
index 0000000000..3eca117f82
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/temporal-zoneddatetime-not-supported.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.formatRangeToParts
+description: Temporal.ZonedDateTime is not supported directly in formatRangeToParts()
+features: [Temporal]
+---*/
+
+const formatter = new Intl.DateTimeFormat();
+
+// Check that TypeError would not be thrown for a different reason
+const {timeZone, ...options} = formatter.resolvedOptions();
+const datetime1 = new Temporal.ZonedDateTime(0n, timeZone);
+assert.sameValue(typeof datetime1.toLocaleString(undefined, options), "string", "toLocaleString() with same options succeeds");
+
+const datetime2 = new Temporal.ZonedDateTime(1_000_000_000n, timeZone);
+assert.throws(TypeError, () => formatter.formatRangeToParts(datetime1, datetime2), "formatRangeToParts() does not support Temporal.ZonedDateTime");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-bad-object.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-bad-object.js
new file mode 100644
index 0000000000..e669dc4421
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-bad-object.js
@@ -0,0 +1,28 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if this is not a DateTimeFormat object
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const formatRange = Intl.DateTimeFormat.prototype.formatRange;
+
+assert.throws(TypeError, function() {
+ formatRange.call({});
+}, "{}");
+
+assert.throws(TypeError, function() {
+ formatRange.call(new Date());
+}, "new Date()");
+
+assert.throws(TypeError, function() {
+ formatRange.call(Intl.DateTimeFormat);
+}, "Intl.DateTimeFormat");
+
+assert.throws(TypeError, function() {
+ formatRange.call(Intl.DateTimeFormat.prototype);
+}, "Intl.DateTimeFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-is-not-object-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-is-not-object-throws.js
new file mode 100644
index 0000000000..970d3e3d33
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRange/this-is-not-object-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if this is not Object.
+info: |
+ Intl.DateTimeFormat.prototype.formatRange ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+
+features: [Intl.DateTimeFormat-formatRange, Symbol]
+---*/
+
+let formatRange = Intl.DateTimeFormat.prototype.formatRange;
+let d1 = new Date("1997-08-22T00:00");
+let d2 = new Date("1999-06-26T00:00");
+
+assert.throws(TypeError, function() {
+ formatRange.call(undefined, d1, d2);
+}, "undefined");
+
+assert.throws(TypeError, function() {
+ formatRange.call(null, d1, d2);
+}, "null");
+
+assert.throws(TypeError, function() {
+ formatRange.call(42, d1, d2);
+}, "number");
+
+assert.throws(TypeError, function() {
+ formatRange.call("foo", d1, d2);
+}, "string");
+
+assert.throws(TypeError, function() {
+ formatRange.call(false, d1, d2);
+}, "false");
+
+assert.throws(TypeError, function() {
+ formatRange.call(true, d1, d2);
+}, "true");
+
+var s = Symbol('3');
+assert.throws(TypeError, function() {
+ formatRange.call(s, d1, d2);
+}, "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-date-string.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-date-string.js
new file mode 100644
index 0000000000..56c7156a63
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-date-string.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ The Date constructor is not called to convert the input value.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const dtf = new Intl.DateTimeFormat();
+const dateTimeString = "2017-11-10T14:09:00.000Z";
+const date = new Date(dateTimeString);
+// |dateTimeString| is valid ISO-8601 style date/time string.
+assert.notSameValue(date, NaN);
+
+// ToNumber() will try to parse the string as an integer and yield NaN, rather
+// than attempting to parse it like the Date constructor would.
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(dateTimeString, date);
+});
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, dateTimeString);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-near-time-boundaries.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-near-time-boundaries.js
new file mode 100644
index 0000000000..ea87893bf1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-near-time-boundaries.js
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ TimeClip is applied when calling Intl.DateTimeFormat.prototype.formatRangeToParts.
+info: |
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+ TimeClip ( time )
+ ...
+ 2. If abs(time) > 8.64 × 10^15, return NaN.
+ ...
+
+includes: [dateConstants.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const dtf = new Intl.DateTimeFormat();
+const date = Date.now();
+
+// Test values near the start of the ECMAScript time range.
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(start_of_time - 1, date);
+});
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, start_of_time - 1);
+});
+assert.sameValue(typeof dtf.formatRangeToParts(start_of_time, date), "object");
+assert.sameValue(typeof dtf.formatRangeToParts(start_of_time + 1, date), "object");
+
+// Test values near the end of the ECMAScript time range.
+assert.sameValue(typeof dtf.formatRangeToParts(date, end_of_time - 1), "object");
+assert.sameValue(typeof dtf.formatRangeToParts(date, end_of_time), "object");
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(end_of_time + 1, date);
+});
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, end_of_time + 1);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-to-integer.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-to-integer.js
new file mode 100644
index 0000000000..df97c735a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-to-integer.js
@@ -0,0 +1,56 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// Copyright (C) 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ TimeClip applies ToInteger on its input value.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+
+ TimeClip ( time )
+ ...
+ 3. Let clippedTime be ! ToInteger(time).
+ 4. If clippedTime is -0, set clippedTime to +0.
+ 5. Return clippedTime.
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+function* zip(a, b) {
+ assert.sameValue(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ assert.sameValue(actualEntry.type, expectedEntry.type, `${message}: type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `${message}: value for entry ${i}`);
+ assert.sameValue(actualEntry.source, expectedEntry.source, `${message}: source for entry ${i}`);
+ }
+}
+
+// Switch to a time format instead of using DateTimeFormat's default date-only format.
+const dtf = new Intl.DateTimeFormat(undefined, {
+ hour: "numeric", minute: "numeric", second: "numeric"
+});
+const date = Date.now();
+const expected = dtf.formatRangeToParts(0, date);
+
+compare(dtf.formatRangeToParts(-0.9, date), expected, "formatRangeToParts(-0.9)");
+compare(dtf.formatRangeToParts(-0.5, date), expected, "formatRangeToParts(-0.5)");
+compare(dtf.formatRangeToParts(-0.1, date), expected, "formatRangeToParts(-0.1)");
+compare(dtf.formatRangeToParts(-Number.MIN_VALUE, date), expected, "formatRangeToParts(-Number.MIN_VALUE)");
+compare(dtf.formatRangeToParts(-0, date), expected, "formatRangeToParts(-0)");
+compare(dtf.formatRangeToParts(+0, date), expected, "formatRangeToParts(+0)");
+compare(dtf.formatRangeToParts(Number.MIN_VALUE, date), expected, "formatRangeToParts(Number.MIN_VALUE)");
+compare(dtf.formatRangeToParts(0.1, date), expected, "formatRangeToParts(0.1)");
+compare(dtf.formatRangeToParts(0.5, date), expected, "formatRangeToParts(0.5)");
+compare(dtf.formatRangeToParts(0.9, date), expected, "formatRangeToParts(0.9)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-tonumber-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-tonumber-throws.js
new file mode 100644
index 0000000000..d38b20da1c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/argument-tonumber-throws.js
@@ -0,0 +1,56 @@
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Return abrupt completions from ToNumber(date)
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+features: [Symbol,Intl.DateTimeFormat-formatRange]
+---*/
+
+const date = Date.now();
+
+const objectValueOf = {
+ valueOf: function() {
+ throw new Test262Error();
+ }
+};
+
+const objectToString = {
+ toString: function() {
+ throw new Test262Error();
+ }
+};
+
+const dtf = new Intl.DateTimeFormat(["pt-BR"]);
+
+assert.throws(Test262Error, function() {
+ dtf.formatRangeToParts(objectValueOf, date);
+}, "valueOf start");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRangeToParts(date, objectValueOf);
+}, "valueOf end");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRangeToParts(objectToString, date);
+}, "toString start");
+
+assert.throws(Test262Error, function() {
+ dtf.formatRangeToParts(date, objectToString);
+}, "toString end");
+
+const s = Symbol('1');
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(s, date);
+}, "symbol start");
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(date, s);
+}, "symbol end");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/builtin.js
new file mode 100644
index 0000000000..840c232f07
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/builtin.js
@@ -0,0 +1,32 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+ Tests that the Intl.DateTimeFormat.prototype.formatRangeToParts function meets the
+ requirements for built-in objects defined by the ECMAScript Language
+ Specification.
+includes: [isConstructor.js]
+features: [Reflect.construct,Intl.DateTimeFormat-formatRange]
+---*/
+
+const formatRangeToParts = Intl.DateTimeFormat.prototype.formatRangeToParts;
+
+assert.sameValue(Object.prototype.toString.call(formatRangeToParts), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatRangeToParts),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatRangeToParts), Function.prototype);
+
+assert.sameValue(formatRangeToParts.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatRangeToParts), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-infinity-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-infinity-throws.js
new file mode 100644
index 0000000000..9074bc63f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-infinity-throws.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to an Infinity value
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a RangeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 7. If x is greater than y, throw a RangeError exception.
+ 8. Return ? FormatDateTimeRangeToParts(dtf, x, y).
+
+ FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
+
+ 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+ TimeClip ( time )
+ 1. If time is not finite, return NaN.
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var date = new Date();
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(Infinity, date);
+}, "+Infinity/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(-Infinity, date);
+}, "-Infinity/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, Infinity);
+}, "date/+Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, -Infinity);
+}, "date/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(Infinity, Infinity);
+}, "+Infinity/+Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(-Infinity, -Infinity);
+}, "-Infinity/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(Infinity, -Infinity);
+}, "+Infinity/-Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(-Infinity, Infinity);
+}, "-Infinity/+Infinity");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-nan-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-nan-throws.js
new file mode 100644
index 0000000000..a96a0f3d70
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-is-nan-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to Nan
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a RangeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+ 7. If x is greater than y, throw a RangeError exception.
+ 8. Return ? FormatDateTimeRangeToParts(dtf, x, y).
+
+ FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
+
+ 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var date = new Date();
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(NaN, date);
+}, "NaN/date");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(date, NaN);
+}, "date/NaN");
+
+assert.throws(RangeError, function() {
+ dtf.formatRangeToParts(NaN, NaN);
+}, "NaN/NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-same-returns-single-date.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-same-returns-single-date.js
new file mode 100644
index 0000000000..551a1fddcc
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-same-returns-single-date.js
@@ -0,0 +1,78 @@
+// Copyright 2021 Google Inc. All rights reserved.
+// Copyright 2021 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: >
+ When startDate is equal to endDate, the output should be an Array of objects with the
+ same value for the `type` and `value` fields as in the Array returned by
+ Intl.DateTimeFormat.prototype.formatToParts.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 4. Let x be ? ToNumber(startDate).
+ 5. Let y be ? ToNumber(endDate).
+ 6. Return ? FormatDateTimeRange(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+
+ 13. If dateFieldsPracticallyEqual is true, then
+ a. Let pattern be dateTimeFormat.[[Pattern]].
+ b. Let patternParts be PartitionPattern(pattern).
+ c. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, tm1).
+ d. For each r in result do
+ i. Set r.[[Source]] to "shared".
+ e. Return result.
+
+features: [Intl.DateTimeFormat-formatRange]
+locale: [en-US]
+---*/
+
+function* zip(a, b) {
+ assert.sameValue(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ }
+}
+
+{
+ const date = new Date(2019, 7, 10, 1, 2, 3, 234);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date), "same output with date options");
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date), "same output with time options");
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date), "same output with date-time options");
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date), "same output with dateStyle/timeStyle");
+}
+{
+ const date1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+ const date2 = new Date(2019, 7, 10, 1, 2, 3, 235);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1), "same output with date options");
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1), "same output with time options");
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1), "same output with date-time options");
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1), "same output with dateStyle/timeStyle");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-undefined-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-undefined-throws.js
new file mode 100644
index 0000000000..5c3e06dd10
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-undefined-throws.js
@@ -0,0 +1,43 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if startDate or endDate are undefined.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+ 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
+ 4. If startDate is undefined or endDate is undefined, throw a TypeError exception.
+ 5. Let x be ? ToNumber(startDate).
+ 6. Let y be ? ToNumber(endDate).
+
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+var dtf = new Intl.DateTimeFormat();
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(); // Not possible to poison this one
+}, "no args");
+
+var poison = { valueOf() { throw new Test262Error(); } };
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(undefined, poison);
+}, "date/undefined");
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(poison, undefined);
+}, "undefined/date");
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(poison);
+}, "only one arg");
+
+assert.throws(TypeError, function() {
+ dtf.formatRangeToParts(undefined, undefined);
+}, "undefined/undefined");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-x-greater-than-y-not-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-x-greater-than-y-not-throws.js
new file mode 100644
index 0000000000..ae527481cd
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/date-x-greater-than-y-not-throws.js
@@ -0,0 +1,33 @@
+// Copyright 2022 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Return an object if date x is greater than y.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 4. Let x be ? ToNumber(startDate).
+ 5. Let y be ? ToNumber(endDate).
+ 6. Return ? FormatDateTimeRangeToParts(dtf, x, y).
+
+ PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. Let y be TimeClip(y).
+ 4. If y is NaN, throw a RangeError exception.
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var x = new Date();
+var y = new Date();
+x.setDate(y.getDate() + 1);
+
+assert.sameValue("object", typeof dtf.formatRangeToParts(x, y));
+assert.sameValue("object", typeof dtf.formatRangeToParts(x, x));
+assert.sameValue("object", typeof dtf.formatRangeToParts(y, y));
+assert.sameValue("object", typeof dtf.formatRangeToParts(y, x));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/en-US.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/en-US.js
new file mode 100644
index 0000000000..559d8eb2fd
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/en-US.js
@@ -0,0 +1,208 @@
+// Copyright (C) 2019 the V8 project authors, Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimerangepattern
+description: Basic tests for the en-US output of formatRangeToParts()
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 8. Return ? FormatDateTimeRange(dtf, x, y).
+locale: [en-US]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === "literal" && part.source === "shared").value;
+
+function* zip(a, b) {
+ assert.sameValue(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue(actualEntry.source, expectedEntry.source, `source for entry ${i}`);
+ }
+}
+
+const date1 = new Date("2019-01-03T00:00:00");
+const date2 = new Date("2019-01-05T00:00:00");
+const date3 = new Date("2019-03-04T00:00:00");
+const date4 = new Date("2020-03-04T00:00:00");
+
+let dtf = new Intl.DateTimeFormat("en-US");
+compare(dtf.formatRangeToParts(date1, date1), [
+ { type: "month", value: "1", source: "shared" },
+ { type: "literal", value: "/", source: "shared" },
+ { type: "day", value: "3", source: "shared" },
+ { type: "literal", value: "/", source: "shared" },
+ { type: "year", value: "2019", source: "shared" },
+]);
+compare(dtf.formatRangeToParts(date1, date2), [
+ { type: "month", value: "1", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "1", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "5", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2019", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date1, date3), [
+ { type: "month", value: "1", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "3", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2019", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date1, date4), [
+ { type: "month", value: "1", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "3", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date2, date3), [
+ { type: "month", value: "1", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "5", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "3", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2019", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date2, date4), [
+ { type: "month", value: "1", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "5", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "3", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date3, date4), [
+ { type: "month", value: "3", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "4", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "3", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+
+dtf = new Intl.DateTimeFormat("en-US", {year: "numeric", month: "short", day: "numeric"});
+compare(dtf.formatRangeToParts(date1, date1), [
+ { type: "month", value: "Jan", source: "shared" },
+ { type: "literal", value: " ", source: "shared" },
+ { type: "day", value: "3", source: "shared" },
+ { type: "literal", value: ", ", source: "shared" },
+ { type: "year", value: "2019", source: "shared" },
+]);
+compare(dtf.formatRangeToParts(date1, date2), [
+ { type: "month", value: "Jan", source: "shared" },
+ { type: "literal", value: " ", source: "shared" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "day", value: "5", source: "endRange" },
+ { type: "literal", value: ", ", source: "shared" },
+ { type: "year", value: "2019", source: "shared" },
+]);
+compare(dtf.formatRangeToParts(date1, date3), [
+ { type: "month", value: "Jan", source: "startRange" },
+ { type: "literal", value: " ", source: "startRange" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "Mar", source: "endRange" },
+ { type: "literal", value: " ", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: ", ", source: "shared" },
+ { type: "year", value: "2019", source: "shared" },
+]);
+compare(dtf.formatRangeToParts(date1, date4), [
+ { type: "month", value: "Jan", source: "startRange" },
+ { type: "literal", value: " ", source: "startRange" },
+ { type: "day", value: "3", source: "startRange" },
+ { type: "literal", value: ", ", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "Mar", source: "endRange" },
+ { type: "literal", value: " ", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: ", ", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date2, date3), [
+ { type: "month", value: "Jan", source: "startRange" },
+ { type: "literal", value: " ", source: "startRange" },
+ { type: "day", value: "5", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "Mar", source: "endRange" },
+ { type: "literal", value: " ", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: ", ", source: "shared" },
+ { type: "year", value: "2019", source: "shared" },
+]);
+compare(dtf.formatRangeToParts(date2, date4), [
+ { type: "month", value: "Jan", source: "startRange" },
+ { type: "literal", value: " ", source: "startRange" },
+ { type: "day", value: "5", source: "startRange" },
+ { type: "literal", value: ", ", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "Mar", source: "endRange" },
+ { type: "literal", value: " ", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: ", ", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+compare(dtf.formatRangeToParts(date3, date4), [
+ { type: "month", value: "Mar", source: "startRange" },
+ { type: "literal", value: " ", source: "startRange" },
+ { type: "day", value: "4", source: "startRange" },
+ { type: "literal", value: ", ", source: "startRange" },
+ { type: "year", value: "2019", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "Mar", source: "endRange" },
+ { type: "literal", value: " ", source: "endRange" },
+ { type: "day", value: "4", source: "endRange" },
+ { type: "literal", value: ", ", source: "endRange" },
+ { type: "year", value: "2020", source: "endRange" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/fractionalSecondDigits.js
new file mode 100644
index 0000000000..0840f80d25
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/fractionalSecondDigits.js
@@ -0,0 +1,160 @@
+// Copyright 2020 Google Inc, Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of fractionalSecondDigits.
+features: [Intl.DateTimeFormat-fractionalSecondDigits, Intl.DateTimeFormat-formatRange]
+locale: [en-US]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDateRangeSeparator = new Intl.DateTimeFormat("en-US", { dateStyle: "short" })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === "literal" && part.source === "shared").value;
+
+function* zip(a, b) {
+ assert.sameValue(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue(actualEntry.source, expectedEntry.source, `source for entry ${i}`);
+ }
+}
+
+const d1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+const d2 = new Date(2019, 7, 10, 1, 2, 3, 567);
+const d3 = new Date(2019, 7, 10, 1, 2, 13, 987);
+
+assert.throws(RangeError, () => {
+ new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 0});
+ }, "fractionalSecondDigits 0 should throw RangeError for out of range");
+
+assert.throws(RangeError, () => {
+ new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 4});
+ }, "fractionalSecondDigits 4 should throw RangeError for out of range");
+
+let dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: undefined});
+
+compare(dtf.formatRangeToParts(d1, d2), [
+ { type: "minute", value: "02", source: "shared" },
+ { type: "literal", value: ":", source: "shared" },
+ { type: "second", value: "03", source: "shared" }
+]);
+
+compare(dtf.formatRangeToParts(d1, d3), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "13", source: "endRange" }
+]);
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 1});
+
+compare(dtf.formatRangeToParts(d1, d2), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "2", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "03", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "5", source: "endRange" }
+]);
+
+compare(dtf.formatRangeToParts(d1, d3), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "2", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "13", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "9", source: "endRange" }
+]);
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 2});
+
+compare(dtf.formatRangeToParts(d1, d2), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "23", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "03", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "56", source: "endRange" }
+]);
+
+compare(dtf.formatRangeToParts(d1, d3), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "23", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "13", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "98", source: "endRange" }
+]);
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 3});
+
+compare(dtf.formatRangeToParts(d1, d2), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "234", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "03", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "567", source: "endRange" }
+]);
+
+compare(dtf.formatRangeToParts(d1, d3), [
+ { type: "minute", value: "02", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "03", source: "startRange" },
+ { type: "literal", value: ".", source: "startRange" },
+ { type: "fractionalSecond", value: "234", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "minute", value: "02", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "13", source: "endRange" },
+ { type: "literal", value: ".", source: "endRange" },
+ { type: "fractionalSecond", value: "987", source: "endRange" }
+]);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/length.js
new file mode 100644
index 0000000000..0a41d864dc
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/length.js
@@ -0,0 +1,16 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatRangeToParts.length.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatRangeToParts, 'length', {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/name.js
new file mode 100644
index 0000000000..0d857a6e83
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/name.js
@@ -0,0 +1,16 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatRangeToParts.name value and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatRangeToParts, 'name', {
+ value: 'formatRangeToParts',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/pattern-on-calendar.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/pattern-on-calendar.js
new file mode 100644
index 0000000000..f36730d122
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/pattern-on-calendar.js
@@ -0,0 +1,40 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks the DateTimeFormat choose different patterns based
+ on calendar.
+includes: [testIntl.js]
+features: [Intl.DateTimeFormat-formatRange]
+locale: [en]
+---*/
+
+let calendars = allCalendars();
+let date1 = new Date(2017, 3, 12);
+let date2 = new Date();
+
+// serialize parts to a string by considering only the type and literal.
+function serializeTypesAndLiteral(parts) {
+ let types = parts.map(part => {
+ if (part.type == "literal") {
+ return `${part.type}(${part.value})`;
+ }
+ return part.type;
+ });
+ return types.join(":");
+}
+
+let df = new Intl.DateTimeFormat("en");
+let base = serializeTypesAndLiteral(df.formatRangeToParts(date1, date2));
+
+const foundDifferentPattern = calendars.some(function(calendar) {
+ let cdf = new Intl.DateTimeFormat("en-u-ca-" + calendar);
+ return base != serializeTypesAndLiteral(cdf.formatRangeToParts(date1, date2));
+});
+
+// Expect at least some calendar use different pattern.
+assert.sameValue(foundDifferentPattern, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/prop-desc.js
new file mode 100644
index 0000000000..edd871b1ad
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/prop-desc.js
@@ -0,0 +1,23 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// Copyright 2019 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Property type and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+assert.sameValue(
+ typeof Intl.DateTimeFormat.prototype.formatRangeToParts,
+ 'function',
+ '`typeof Intl.DateTimeFormat.prototype.formatRangeToParts` is `function`'
+);
+
+verifyProperty(Intl.DateTimeFormat.prototype, 'formatRangeToParts', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/shell.js
new file mode 100644
index 0000000000..99241dca55
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/shell.js
@@ -0,0 +1,384 @@
+// GENERATED, DO NOT EDIT
+// file: dateConstants.js
+// Copyright (C) 2009 the Sputnik authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Collection of date-centric values
+defines:
+ - date_1899_end
+ - date_1900_start
+ - date_1969_end
+ - date_1970_start
+ - date_1999_end
+ - date_2000_start
+ - date_2099_end
+ - date_2100_start
+ - start_of_time
+ - end_of_time
+---*/
+
+var date_1899_end = -2208988800001;
+var date_1900_start = -2208988800000;
+var date_1969_end = -1;
+var date_1970_start = 0;
+var date_1999_end = 946684799999;
+var date_2000_start = 946684800000;
+var date_2099_end = 4102444799999;
+var date_2100_start = 4102444800000;
+
+var start_of_time = -8.64e15;
+var end_of_time = 8.64e15;
+
+// file: deepEqual.js
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: >
+ Compare two values structurally
+defines: [assert.deepEqual]
+---*/
+
+assert.deepEqual = function(actual, expected, message) {
+ var format = assert.deepEqual.format;
+ assert(
+ assert.deepEqual._compare(actual, expected),
+ `Expected ${format(actual)} to be structurally equal to ${format(expected)}. ${(message || '')}`
+ );
+};
+
+assert.deepEqual.format = function(value, seen) {
+ switch (typeof value) {
+ case 'string':
+ return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
+ case 'number':
+ case 'boolean':
+ case 'symbol':
+ case 'bigint':
+ return value.toString();
+ case 'undefined':
+ return 'undefined';
+ case 'function':
+ return `[Function${value.name ? `: ${value.name}` : ''}]`;
+ case 'object':
+ if (value === null) return 'null';
+ if (value instanceof Date) return `Date "${value.toISOString()}"`;
+ if (value instanceof RegExp) return value.toString();
+ if (!seen) {
+ seen = {
+ counter: 0,
+ map: new Map()
+ };
+ }
+
+ let usage = seen.map.get(value);
+ if (usage) {
+ usage.used = true;
+ return `[Ref: #${usage.id}]`;
+ }
+
+ usage = { id: ++seen.counter, used: false };
+ seen.map.set(value, usage);
+
+ if (typeof Set !== "undefined" && value instanceof Set) {
+ return `Set {${Array.from(value).map(value => assert.deepEqual.format(value, seen)).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ if (typeof Map !== "undefined" && value instanceof Map) {
+ return `Map {${Array.from(value).map(pair => `${assert.deepEqual.format(pair[0], seen)} => ${assert.deepEqual.format(pair[1], seen)}}`).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
+ return `[${value.map(value => assert.deepEqual.format(value, seen)).join(', ')}]${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ let tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
+ if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
+ tag = '[Object: null prototype]';
+ }
+ return `${tag ? `${tag} ` : ''}{ ${Object.keys(value).map(key => `${key.toString()}: ${assert.deepEqual.format(value[key], seen)}`).join(', ')} }${usage.used ? ` as #${usage.id}` : ''}`;
+ default:
+ return typeof value;
+ }
+};
+
+assert.deepEqual._compare = (function () {
+ var EQUAL = 1;
+ var NOT_EQUAL = -1;
+ var UNKNOWN = 0;
+
+ function deepEqual(a, b) {
+ return compareEquality(a, b) === EQUAL;
+ }
+
+ function compareEquality(a, b, cache) {
+ return compareIf(a, b, isOptional, compareOptionality)
+ || compareIf(a, b, isPrimitiveEquatable, comparePrimitiveEquality)
+ || compareIf(a, b, isObjectEquatable, compareObjectEquality, cache)
+ || NOT_EQUAL;
+ }
+
+ function compareIf(a, b, test, compare, cache) {
+ return !test(a)
+ ? !test(b) ? UNKNOWN : NOT_EQUAL
+ : !test(b) ? NOT_EQUAL : cacheComparison(a, b, compare, cache);
+ }
+
+ function tryCompareStrictEquality(a, b) {
+ return a === b ? EQUAL : UNKNOWN;
+ }
+
+ function tryCompareTypeOfEquality(a, b) {
+ return typeof a !== typeof b ? NOT_EQUAL : UNKNOWN;
+ }
+
+ function tryCompareToStringTagEquality(a, b) {
+ var aTag = Symbol.toStringTag in a ? a[Symbol.toStringTag] : undefined;
+ var bTag = Symbol.toStringTag in b ? b[Symbol.toStringTag] : undefined;
+ return aTag !== bTag ? NOT_EQUAL : UNKNOWN;
+ }
+
+ function isOptional(value) {
+ return value === undefined
+ || value === null;
+ }
+
+ function compareOptionality(a, b) {
+ return tryCompareStrictEquality(a, b)
+ || NOT_EQUAL;
+ }
+
+ function isPrimitiveEquatable(value) {
+ switch (typeof value) {
+ case 'string':
+ case 'number':
+ case 'bigint':
+ case 'boolean':
+ case 'symbol':
+ return true;
+ default:
+ return isBoxed(value);
+ }
+ }
+
+ function comparePrimitiveEquality(a, b) {
+ if (isBoxed(a)) a = a.valueOf();
+ if (isBoxed(b)) b = b.valueOf();
+ return tryCompareStrictEquality(a, b)
+ || tryCompareTypeOfEquality(a, b)
+ || compareIf(a, b, isNaNEquatable, compareNaNEquality)
+ || NOT_EQUAL;
+ }
+
+ function isNaNEquatable(value) {
+ return typeof value === 'number';
+ }
+
+ function compareNaNEquality(a, b) {
+ return isNaN(a) && isNaN(b) ? EQUAL : NOT_EQUAL;
+ }
+
+ function isObjectEquatable(value) {
+ return typeof value === 'object';
+ }
+
+ function compareObjectEquality(a, b, cache) {
+ if (!cache) cache = new Map();
+ return getCache(cache, a, b)
+ || setCache(cache, a, b, EQUAL) // consider equal for now
+ || cacheComparison(a, b, tryCompareStrictEquality, cache)
+ || cacheComparison(a, b, tryCompareToStringTagEquality, cache)
+ || compareIf(a, b, isValueOfEquatable, compareValueOfEquality)
+ || compareIf(a, b, isToStringEquatable, compareToStringEquality)
+ || compareIf(a, b, isArrayLikeEquatable, compareArrayLikeEquality, cache)
+ || compareIf(a, b, isStructurallyEquatable, compareStructuralEquality, cache)
+ || compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || cacheComparison(a, b, fail, cache);
+ }
+
+ function isBoxed(value) {
+ return value instanceof String
+ || value instanceof Number
+ || value instanceof Boolean
+ || typeof Symbol === 'function' && value instanceof Symbol
+ || typeof BigInt === 'function' && value instanceof BigInt;
+ }
+
+ function isValueOfEquatable(value) {
+ return value instanceof Date;
+ }
+
+ function compareValueOfEquality(a, b) {
+ return compareIf(a.valueOf(), b.valueOf(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ function isToStringEquatable(value) {
+ return value instanceof RegExp;
+ }
+
+ function compareToStringEquality(a, b) {
+ return compareIf(a.toString(), b.toString(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ function isArrayLikeEquatable(value) {
+ return (Array.isArray ? Array.isArray(value) : value instanceof Array)
+ || (typeof Uint8Array === 'function' && value instanceof Uint8Array)
+ || (typeof Uint8ClampedArray === 'function' && value instanceof Uint8ClampedArray)
+ || (typeof Uint16Array === 'function' && value instanceof Uint16Array)
+ || (typeof Uint32Array === 'function' && value instanceof Uint32Array)
+ || (typeof Int8Array === 'function' && value instanceof Int8Array)
+ || (typeof Int16Array === 'function' && value instanceof Int16Array)
+ || (typeof Int32Array === 'function' && value instanceof Int32Array)
+ || (typeof Float32Array === 'function' && value instanceof Float32Array)
+ || (typeof Float64Array === 'function' && value instanceof Float64Array)
+ || (typeof BigUint64Array === 'function' && value instanceof BigUint64Array)
+ || (typeof BigInt64Array === 'function' && value instanceof BigInt64Array);
+ }
+
+ function compareArrayLikeEquality(a, b, cache) {
+ if (a.length !== b.length) return NOT_EQUAL;
+ for (var i = 0; i < a.length; i++) {
+ if (compareEquality(a[i], b[i], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+ return EQUAL;
+ }
+
+ function isStructurallyEquatable(value) {
+ return !(typeof Promise === 'function' && value instanceof Promise // only comparable by reference
+ || typeof WeakMap === 'function' && value instanceof WeakMap // only comparable by reference
+ || typeof WeakSet === 'function' && value instanceof WeakSet // only comparable by reference
+ || typeof Map === 'function' && value instanceof Map // comparable via @@iterator
+ || typeof Set === 'function' && value instanceof Set); // comparable via @@iterator
+ }
+
+ function compareStructuralEquality(a, b, cache) {
+ var aKeys = [];
+ for (var key in a) aKeys.push(key);
+
+ var bKeys = [];
+ for (var key in b) bKeys.push(key);
+
+ if (aKeys.length !== bKeys.length) {
+ return NOT_EQUAL;
+ }
+
+ aKeys.sort();
+ bKeys.sort();
+
+ for (var i = 0; i < aKeys.length; i++) {
+ var aKey = aKeys[i];
+ var bKey = bKeys[i];
+ if (compareEquality(aKey, bKey, cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ if (compareEquality(a[aKey], b[bKey], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+
+ return compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || EQUAL;
+ }
+
+ function isIterableEquatable(value) {
+ return typeof Symbol === 'function'
+ && typeof value[Symbol.iterator] === 'function';
+ }
+
+ function compareIteratorEquality(a, b, cache) {
+ if (typeof Map === 'function' && a instanceof Map && b instanceof Map ||
+ typeof Set === 'function' && a instanceof Set && b instanceof Set) {
+ if (a.size !== b.size) return NOT_EQUAL; // exit early if we detect a difference in size
+ }
+
+ var ar, br;
+ while (true) {
+ ar = a.next();
+ br = b.next();
+ if (ar.done) {
+ if (br.done) return EQUAL;
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ if (br.done) {
+ if (a.return) a.return();
+ return NOT_EQUAL;
+ }
+ if (compareEquality(ar.value, br.value, cache) === NOT_EQUAL) {
+ if (a.return) a.return();
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ }
+ }
+
+ function compareIterableEquality(a, b, cache) {
+ return compareIteratorEquality(a[Symbol.iterator](), b[Symbol.iterator](), cache);
+ }
+
+ function cacheComparison(a, b, compare, cache) {
+ var result = compare(a, b, cache);
+ if (cache && (result === EQUAL || result === NOT_EQUAL)) {
+ setCache(cache, a, b, /** @type {EQUAL | NOT_EQUAL} */(result));
+ }
+ return result;
+ }
+
+ function fail() {
+ return NOT_EQUAL;
+ }
+
+ function setCache(cache, left, right, result) {
+ var otherCache;
+
+ otherCache = cache.get(left);
+ if (!otherCache) cache.set(left, otherCache = new Map());
+ otherCache.set(right, result);
+
+ otherCache = cache.get(right);
+ if (!otherCache) cache.set(right, otherCache = new Map());
+ otherCache.set(left, result);
+ }
+
+ function getCache(cache, left, right) {
+ var otherCache;
+ var result;
+
+ otherCache = cache.get(left);
+ result = otherCache && otherCache.get(right);
+ if (result) return result;
+
+ otherCache = cache.get(right);
+ result = otherCache && otherCache.get(left);
+ if (result) return result;
+
+ return UNKNOWN;
+ }
+
+ return deepEqual;
+})();
+
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js
new file mode 100644
index 0000000000..ec34d8e9a9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-resolved-time-zone.js
@@ -0,0 +1,117 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-datetime-format-functions
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+includes: [deepEqual.js]
+features: [Temporal, Intl.DateTimeFormat-formatRange]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDayPeriodSpace =
+ new Intl.DateTimeFormat('en-US', { timeStyle: 'short' })
+ .formatToParts(0)
+ .find((part, i, parts) => part.type === 'literal' && parts[i + 1].type === 'dayPeriod')?.value || '';
+const usDateRangeSeparator = new Intl.DateTimeFormat('en-US', { dateStyle: 'short' })
+ .formatRangeToParts(1 * 86400 * 1000, 366 * 86400 * 1000)
+ .find((part) => part.type === 'literal' && part.source === 'shared').value;
+
+const formatter = new Intl.DateTimeFormat('en-US', { timeZone: 'Pacific/Apia' });
+
+const date1 = new Temporal.PlainDate(2021, 8, 4);
+const date2 = new Temporal.PlainDate(2021, 8, 5);
+const dateResult = formatter.formatRangeToParts(date1, date2);
+assert.deepEqual(dateResult, [
+ { type: "month", value: "8", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "4", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2021", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "8", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "5", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2021", source: "endRange" },
+], "plain dates");
+
+const datetime1 = new Temporal.PlainDateTime(2021, 8, 4, 0, 30, 45, 123, 456, 789);
+const datetime2 = new Temporal.PlainDateTime(2021, 8, 4, 23, 30, 45, 123, 456, 789);
+const datetimeResult = formatter.formatRangeToParts(datetime1, datetime2);
+assert.deepEqual(datetimeResult, [
+ { type: "month", value: "8", source: "shared" },
+ { type: "literal", value: "/", source: "shared" },
+ { type: "day", value: "4", source: "shared" },
+ { type: "literal", value: "/", source: "shared" },
+ { type: "year", value: "2021", source: "shared" },
+ { type: "literal", value: ", ", source: "shared" },
+ { type: "hour", value: "12", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "minute", value: "30", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "45", source: "startRange" },
+ { type: "literal", value: usDayPeriodSpace, source: "startRange" },
+ { type: "dayPeriod", value: "AM", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "hour", value: "11", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "minute", value: "30", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "45", source: "endRange" },
+ { type: "literal", value: usDayPeriodSpace, source: "endRange" },
+ { type: "dayPeriod", value: "PM", source: "endRange" },
+], "plain datetimes");
+
+const monthDay1 = new Temporal.PlainMonthDay(8, 4, "gregory");
+const monthDay2 = new Temporal.PlainMonthDay(8, 5, "gregory");
+const monthDayResult = formatter.formatRangeToParts(monthDay1, monthDay2);
+assert.deepEqual(monthDayResult, [
+ { type: "month", value: "8", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "day", value: "4", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "8", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "day", value: "5", source: "endRange" },
+], "plain month-days");
+
+const time1 = new Temporal.PlainTime(0, 30, 45, 123, 456, 789);
+const time2 = new Temporal.PlainTime(23, 30, 45, 123, 456, 789);
+const timeResult = formatter.formatRangeToParts(time1, time2);
+assert.deepEqual(timeResult, [
+ { type: "hour", value: "12", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "minute", value: "30", source: "startRange" },
+ { type: "literal", value: ":", source: "startRange" },
+ { type: "second", value: "45", source: "startRange" },
+ { type: "literal", value: usDayPeriodSpace, source: "startRange" },
+ { type: "dayPeriod", value: "AM", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "hour", value: "11", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "minute", value: "30", source: "endRange" },
+ { type: "literal", value: ":", source: "endRange" },
+ { type: "second", value: "45", source: "endRange" },
+ { type: "literal", value: usDayPeriodSpace, source: "endRange" },
+ { type: "dayPeriod", value: "PM", source: "endRange" },
+], "plain times");
+
+const month1 = new Temporal.PlainYearMonth(2021, 8, "gregory");
+const month2 = new Temporal.PlainYearMonth(2021, 9, "gregory");
+const monthResult = formatter.formatRangeToParts(month1, month2);
+assert.deepEqual(monthResult, [
+ { type: "month", value: "8", source: "startRange" },
+ { type: "literal", value: "/", source: "startRange" },
+ { type: "year", value: "2021", source: "startRange" },
+ { type: "literal", value: usDateRangeSeparator, source: "shared" },
+ { type: "month", value: "9", source: "endRange" },
+ { type: "literal", value: "/", source: "endRange" },
+ { type: "year", value: "2021", source: "endRange" },
+], "plain year-months");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-zoneddatetime-not-supported.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-zoneddatetime-not-supported.js
new file mode 100644
index 0000000000..edc0263d10
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-zoneddatetime-not-supported.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.formatRange
+description: Temporal.ZonedDateTime is not supported directly in formatRange()
+features: [Temporal]
+---*/
+
+const formatter = new Intl.DateTimeFormat();
+
+// Check that TypeError would not be thrown for a different reason
+const {timeZone, ...options} = formatter.resolvedOptions();
+const datetime1 = new Temporal.ZonedDateTime(0n, timeZone);
+assert.sameValue(typeof datetime1.toLocaleString(undefined, options), "string", "toLocaleString() with same options succeeds");
+
+const datetime2 = new Temporal.ZonedDateTime(1_000_000_000n, timeZone);
+assert.throws(TypeError, () => formatter.formatRange(datetime1, datetime2), "formatRange() does not support Temporal.ZonedDateTime");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-bad-object.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-bad-object.js
new file mode 100644
index 0000000000..21f29b4541
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-bad-object.js
@@ -0,0 +1,28 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if this is not a DateTimeFormat object
+features: [Intl.DateTimeFormat-formatRange]
+---*/
+
+const formatRangeToParts = Intl.DateTimeFormat.prototype.formatRangeToParts;
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call({});
+}, "{}");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(new Date());
+}, "new Date()");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(Intl.DateTimeFormat);
+}, "Intl.DateTimeFormat");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(Intl.DateTimeFormat.prototype);
+}, "Intl.DateTimeFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-is-not-object-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-is-not-object-throws.js
new file mode 100644
index 0000000000..7fe4599531
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatRangeToParts/this-is-not-object-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2019 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if this is not Object.
+info: |
+ Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate , endDate )
+
+ 1. Let dtf be this value.
+ 2. If Type(dtf) is not Object, throw a TypeError exception.
+
+features: [Intl.DateTimeFormat-formatRange, Symbol]
+---*/
+
+let formatRangeToParts = Intl.DateTimeFormat.prototype.formatRangeToParts;
+let d1 = new Date("1997-08-22T00:00");
+let d2 = new Date("1999-06-26T00:00");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(undefined, d1, d2);
+}, "undefined");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(null, d1, d2);
+}, "null");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(42, d1, d2);
+}, "number");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call("foo", d1, d2);
+}, "string");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(false, d1, d2);
+}, "false");
+
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(true, d1, d2);
+}, "true");
+
+var s = Symbol('3');
+assert.throws(TypeError, function() {
+ formatRangeToParts.call(s, d1, d2);
+}, "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-constructor-not-called.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-constructor-not-called.js
new file mode 100644
index 0000000000..9c63ee3e52
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-constructor-not-called.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ The Date constructor is not called to convert the input value.
+info: >
+ 12.4.4 Intl.DateTimeFormat.prototype.formatToParts ( date )
+
+ ...
+ 4. If date is undefined, then
+ ...
+ 5. Else,
+ a. Let x be ? ToNumber(date).
+ 5. Return ? FormatDateTimeToParts(dtf, x).
+
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. ...
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+var dateTimeString = "2017-11-10T14:09:00.000Z";
+
+// |dateTimeString| is valid ISO-8601 style date/time string.
+assert.notSameValue(new Date(dateTimeString), NaN);
+
+// Ensure string input values are not converted to time values by calling the
+// Date constructor.
+assert.throws(RangeError, function() {
+ dtf.formatToParts(dateTimeString);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-infinity-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-infinity-throws.js
new file mode 100644
index 0000000000..071c07ed45
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-infinity-throws.js
@@ -0,0 +1,35 @@
+// Copyright 2016 Leonardo Balter. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to an Infinity value
+info: |
+ Intl.DateTimeFormat.prototype.formatToParts ([ date ])
+
+ 4. If _date_ is not provided or is *undefined*, then
+ a. Let _x_ be *%Date_now%*().
+ 5. Else,
+ a. Let _x_ be ? ToNumber(_date_).
+ 6. Return ? FormatDateTimeToParts(_dtf_, _x_).
+
+ FormatDateTimeToParts(dateTimeFormat, x)
+
+ 1. Let _parts_ be ? PartitionDateTimePattern(_dateTimeFormat_, _x_).
+
+ PartitionDateTimePattern (dateTimeFormat, x)
+
+ 1. If _x_ is not a finite Number, throw a *RangeError* exception.
+---*/
+
+var dtf = new Intl.DateTimeFormat(["pt-BR"]);
+
+assert.throws(RangeError, function() {
+ dtf.formatToParts(Infinity);
+}, "+Infinity");
+
+assert.throws(RangeError, function() {
+ dtf.formatToParts(-Infinity);
+}, "-Infinity");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-nan-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-nan-throws.js
new file mode 100644
index 0000000000..ab062734e0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/date-is-nan-throws.js
@@ -0,0 +1,35 @@
+// Copyright 2016 Leonardo Balter. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a RangeError if date arg is cast to NaN
+info: |
+ Intl.DateTimeFormat.prototype.formatToParts ([ date ])
+
+ 4. If _date_ is not provided or is *undefined*, then
+ a. Let _x_ be *%Date_now%*().
+ 5. Else,
+ a. Let _x_ be ? ToNumber(_date_).
+ 6. Return ? FormatDateTimeToParts(_dtf_, _x_).
+
+ FormatDateTimeToParts(dateTimeFormat, x)
+
+ 1. Let _parts_ be ? PartitionDateTimePattern(_dateTimeFormat_, _x_).
+
+ PartitionDateTimePattern (dateTimeFormat, x)
+
+ 1. If _x_ is not a finite Number, throw a *RangeError* exception.
+---*/
+
+var dtf = new Intl.DateTimeFormat(["pt-BR"]);
+
+assert.throws(RangeError, function() {
+ dtf.formatToParts(NaN);
+});
+
+assert.throws(RangeError, function() {
+ dtf.formatToParts("lol");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js
new file mode 100644
index 0000000000..ef2e24f43d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js
@@ -0,0 +1,108 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of dayPeriod, long format.
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const long = new Intl.DateTimeFormat('en', { dayPeriod: 'long' });
+
+function assertParts(parts, expected, message) {
+ assert.sameValue(parts.length, 1, `length should be 1, ${message}`);
+ assert.sameValue(parts[0].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[0].type, 'dayPeriod', `part type is dayPeriod. ${message}`);
+}
+
+assertParts(long.formatToParts(d0000), 'at night', '00:00, long format');
+assertParts(long.formatToParts(d0100), 'at night', '01:00, long format');
+assertParts(long.formatToParts(d0200), 'at night', '02:00, long format');
+assertParts(long.formatToParts(d0300), 'at night', '03:00, long format');
+assertParts(long.formatToParts(d0400), 'at night', '04:00, long format');
+assertParts(long.formatToParts(d0500), 'at night', '05:00, long format');
+assertParts(long.formatToParts(d0600), 'in the morning', '06:00, long format');
+assertParts(long.formatToParts(d0700), 'in the morning', '07:00, long format');
+assertParts(long.formatToParts(d0800), 'in the morning', '08:00, long format');
+assertParts(long.formatToParts(d0900), 'in the morning', '09:00, long format');
+assertParts(long.formatToParts(d1000), 'in the morning', '10:00, long format');
+assertParts(long.formatToParts(d1100), 'in the morning', '11:00, long format');
+assertParts(long.formatToParts(d1200), 'noon', '12:00, long format');
+assertParts(long.formatToParts(d1300), 'in the afternoon', '13:00, long format');
+assertParts(long.formatToParts(d1400), 'in the afternoon', '14:00, long format');
+assertParts(long.formatToParts(d1500), 'in the afternoon', '15:00, long format');
+assertParts(long.formatToParts(d1600), 'in the afternoon', '16:00, long format');
+assertParts(long.formatToParts(d1700), 'in the afternoon', '17:00, long format');
+assertParts(long.formatToParts(d1800), 'in the evening', '18:00, long format');
+assertParts(long.formatToParts(d1900), 'in the evening', '19:00, long format');
+assertParts(long.formatToParts(d2000), 'in the evening', '20:00, long format');
+assertParts(long.formatToParts(d2100), 'at night', '21:00, long format');
+assertParts(long.formatToParts(d2200), 'at night', '22:00, long format');
+assertParts(long.formatToParts(d2300), 'at night', '23:00, long format');
+
+const longNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'long',
+ hour: 'numeric'
+});
+
+function assertPartsNumeric(parts, hour, expected, message) {
+ assert.sameValue(parts.length, 3, `length should be 3, ${message}`);
+ assert.sameValue(parts[0].value, hour, `hour part value. ${message}`);
+ assert.sameValue(parts[0].type, 'hour', `hour part type. ${message}`);
+ assert.sameValue(parts[1].value, ' ', `literal part value. ${message}`);
+ assert.sameValue(parts[1].type, 'literal', `literal part type. ${message}`);
+ assert.sameValue(parts[2].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[2].type, 'dayPeriod', `expected part type. ${message}`);
+}
+
+assertPartsNumeric(longNumeric.formatToParts(d0000), '12', 'at night', '00:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0100), '1', 'at night', '01:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0200), '2', 'at night', '02:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0300), '3', 'at night', '03:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0400), '4', 'at night', '04:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0500), '5', 'at night', '05:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0600), '6', 'in the morning', '06:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0700), '7', 'in the morning', '07:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0800), '8', 'in the morning', '08:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d0900), '9', 'in the morning', '09:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1000), '10', 'in the morning', '10:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1100), '11', 'in the morning', '11:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1200), '12', 'noon', '12:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1300), '1', 'in the afternoon', '13:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1400), '2', 'in the afternoon', '14:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1500), '3', 'in the afternoon', '15:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1600), '4', 'in the afternoon', '16:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1700), '5', 'in the afternoon', '17:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1800), '6', 'in the evening', '18:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d1900), '7', 'in the evening', '19:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d2000), '8', 'in the evening', '20:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d2100), '9', 'at night', '21:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d2200), '10', 'at night', '22:00, long-numeric');
+assertPartsNumeric(longNumeric.formatToParts(d2300), '11', 'at night', '23:00, long-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js
new file mode 100644
index 0000000000..7bcebc2537
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js
@@ -0,0 +1,108 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of dayPeriod, narrow format.
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const narrow = new Intl.DateTimeFormat('en', { dayPeriod: 'narrow' });
+
+function assertParts(parts, expected, message) {
+ assert.sameValue(parts.length, 1, `length should be 1, ${message}`);
+ assert.sameValue(parts[0].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[0].type, 'dayPeriod', `part type is dayPeriod. ${message}`);
+}
+
+assertParts(narrow.formatToParts(d0000), 'at night', '00:00, narrow format');
+assertParts(narrow.formatToParts(d0100), 'at night', '01:00, narrow format');
+assertParts(narrow.formatToParts(d0200), 'at night', '02:00, narrow format');
+assertParts(narrow.formatToParts(d0300), 'at night', '03:00, narrow format');
+assertParts(narrow.formatToParts(d0400), 'at night', '04:00, narrow format');
+assertParts(narrow.formatToParts(d0500), 'at night', '05:00, narrow format');
+assertParts(narrow.formatToParts(d0600), 'in the morning', '06:00, narrow format');
+assertParts(narrow.formatToParts(d0700), 'in the morning', '07:00, narrow format');
+assertParts(narrow.formatToParts(d0800), 'in the morning', '08:00, narrow format');
+assertParts(narrow.formatToParts(d0900), 'in the morning', '09:00, narrow format');
+assertParts(narrow.formatToParts(d1000), 'in the morning', '10:00, narrow format');
+assertParts(narrow.formatToParts(d1100), 'in the morning', '11:00, narrow format');
+assertParts(narrow.formatToParts(d1200), 'n', '12:00, narrow format');
+assertParts(narrow.formatToParts(d1300), 'in the afternoon', '13:00, narrow format');
+assertParts(narrow.formatToParts(d1400), 'in the afternoon', '14:00, narrow format');
+assertParts(narrow.formatToParts(d1500), 'in the afternoon', '15:00, narrow format');
+assertParts(narrow.formatToParts(d1600), 'in the afternoon', '16:00, narrow format');
+assertParts(narrow.formatToParts(d1700), 'in the afternoon', '17:00, narrow format');
+assertParts(narrow.formatToParts(d1800), 'in the evening', '18:00, narrow format');
+assertParts(narrow.formatToParts(d1900), 'in the evening', '19:00, narrow format');
+assertParts(narrow.formatToParts(d2000), 'in the evening', '20:00, narrow format');
+assertParts(narrow.formatToParts(d2100), 'at night', '21:00, narrow format');
+assertParts(narrow.formatToParts(d2200), 'at night', '22:00, narrow format');
+assertParts(narrow.formatToParts(d2300), 'at night', '23:00, narrow format');
+
+const narrowNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'narrow',
+ hour: 'numeric'
+});
+
+function assertPartsNumeric(parts, hour, expected, message) {
+ assert.sameValue(parts.length, 3, `length should be 3, ${message}`);
+ assert.sameValue(parts[0].value, hour, `hour part value. ${message}`);
+ assert.sameValue(parts[0].type, 'hour', `hour part type. ${message}`);
+ assert.sameValue(parts[1].value, ' ', `literal part value. ${message}`);
+ assert.sameValue(parts[1].type, 'literal', `literal part type. ${message}`);
+ assert.sameValue(parts[2].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[2].type, 'dayPeriod', `expected part type. ${message}`);
+}
+
+assertPartsNumeric(narrowNumeric.formatToParts(d0000), '12', 'at night', '00:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0100), '1', 'at night', '01:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0200), '2', 'at night', '02:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0300), '3', 'at night', '03:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0400), '4', 'at night', '04:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0500), '5', 'at night', '05:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0600), '6', 'in the morning', '06:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0700), '7', 'in the morning', '07:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0800), '8', 'in the morning', '08:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d0900), '9', 'in the morning', '09:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1000), '10', 'in the morning', '10:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1100), '11', 'in the morning', '11:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1200), '12', 'n', '12:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1300), '1', 'in the afternoon', '13:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1400), '2', 'in the afternoon', '14:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1500), '3', 'in the afternoon', '15:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1600), '4', 'in the afternoon', '16:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1700), '5', 'in the afternoon', '17:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1800), '6', 'in the evening', '18:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d1900), '7', 'in the evening', '19:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d2000), '8', 'in the evening', '20:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d2100), '9', 'at night', '21:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d2200), '10', 'at night', '22:00, narrow-numeric');
+assertPartsNumeric(narrowNumeric.formatToParts(d2300), '11', 'at night', '23:00, narrow-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js
new file mode 100644
index 0000000000..66ec863b3d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js
@@ -0,0 +1,108 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of dayPeriod, short format.
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const d0000 = new Date(2017, 11, 12, 0, 0, 0, 0);
+const d0100 = new Date(2017, 11, 12, 1, 0, 0, 0);
+const d0200 = new Date(2017, 11, 12, 2, 0, 0, 0);
+const d0300 = new Date(2017, 11, 12, 3, 0, 0, 0);
+const d0400 = new Date(2017, 11, 12, 4, 0, 0, 0);
+const d0500 = new Date(2017, 11, 12, 5, 0, 0, 0);
+const d0600 = new Date(2017, 11, 12, 6, 0, 0, 0);
+const d0700 = new Date(2017, 11, 12, 7, 0, 0, 0);
+const d0800 = new Date(2017, 11, 12, 8, 0, 0, 0);
+const d0900 = new Date(2017, 11, 12, 9, 0, 0, 0);
+const d1000 = new Date(2017, 11, 12, 10, 0, 0, 0);
+const d1100 = new Date(2017, 11, 12, 11, 0, 0, 0);
+const d1200 = new Date(2017, 11, 12, 12, 0, 0, 0);
+const d1300 = new Date(2017, 11, 12, 13, 0, 0, 0);
+const d1400 = new Date(2017, 11, 12, 14, 0, 0, 0);
+const d1500 = new Date(2017, 11, 12, 15, 0, 0, 0);
+const d1600 = new Date(2017, 11, 12, 16, 0, 0, 0);
+const d1700 = new Date(2017, 11, 12, 17, 0, 0, 0);
+const d1800 = new Date(2017, 11, 12, 18, 0, 0, 0);
+const d1900 = new Date(2017, 11, 12, 19, 0, 0, 0);
+const d2000 = new Date(2017, 11, 12, 20, 0, 0, 0);
+const d2100 = new Date(2017, 11, 12, 21, 0, 0, 0);
+const d2200 = new Date(2017, 11, 12, 22, 0, 0, 0);
+const d2300 = new Date(2017, 11, 12, 23, 0, 0, 0);
+
+const short = new Intl.DateTimeFormat('en', { dayPeriod: 'short' });
+
+function assertParts(parts, expected, message) {
+ assert.sameValue(parts.length, 1, `length should be 1, ${message}`);
+ assert.sameValue(parts[0].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[0].type, 'dayPeriod', `part type is dayPeriod. ${message}`);
+}
+
+assertParts(short.formatToParts(d0000), 'at night', '00:00, short format');
+assertParts(short.formatToParts(d0100), 'at night', '01:00, short format');
+assertParts(short.formatToParts(d0200), 'at night', '02:00, short format');
+assertParts(short.formatToParts(d0300), 'at night', '03:00, short format');
+assertParts(short.formatToParts(d0400), 'at night', '04:00, short format');
+assertParts(short.formatToParts(d0500), 'at night', '05:00, short format');
+assertParts(short.formatToParts(d0600), 'in the morning', '06:00, short format');
+assertParts(short.formatToParts(d0700), 'in the morning', '07:00, short format');
+assertParts(short.formatToParts(d0800), 'in the morning', '08:00, short format');
+assertParts(short.formatToParts(d0900), 'in the morning', '09:00, short format');
+assertParts(short.formatToParts(d1000), 'in the morning', '10:00, short format');
+assertParts(short.formatToParts(d1100), 'in the morning', '11:00, short format');
+assertParts(short.formatToParts(d1200), 'noon', '12:00, short format');
+assertParts(short.formatToParts(d1300), 'in the afternoon', '13:00, short format');
+assertParts(short.formatToParts(d1400), 'in the afternoon', '14:00, short format');
+assertParts(short.formatToParts(d1500), 'in the afternoon', '15:00, short format');
+assertParts(short.formatToParts(d1600), 'in the afternoon', '16:00, short format');
+assertParts(short.formatToParts(d1700), 'in the afternoon', '17:00, short format');
+assertParts(short.formatToParts(d1800), 'in the evening', '18:00, short format');
+assertParts(short.formatToParts(d1900), 'in the evening', '19:00, short format');
+assertParts(short.formatToParts(d2000), 'in the evening', '20:00, short format');
+assertParts(short.formatToParts(d2100), 'at night', '21:00, short format');
+assertParts(short.formatToParts(d2200), 'at night', '22:00, short format');
+assertParts(short.formatToParts(d2300), 'at night', '23:00, short format');
+
+const shortNumeric = new Intl.DateTimeFormat('en', {
+ dayPeriod: 'short',
+ hour: 'numeric'
+});
+
+function assertPartsNumeric(parts, hour, expected, message) {
+ assert.sameValue(parts.length, 3, `length should be 3, ${message}`);
+ assert.sameValue(parts[0].value, hour, `hour part value. ${message}`);
+ assert.sameValue(parts[0].type, 'hour', `hour part type. ${message}`);
+ assert.sameValue(parts[1].value, ' ', `literal part value. ${message}`);
+ assert.sameValue(parts[1].type, 'literal', `literal part type. ${message}`);
+ assert.sameValue(parts[2].value, expected, `expected part value. ${message}`);
+ assert.sameValue(parts[2].type, 'dayPeriod', `expected part type. ${message}`);
+}
+
+assertPartsNumeric(shortNumeric.formatToParts(d0000), '12', 'at night', '00:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0100), '1', 'at night', '01:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0200), '2', 'at night', '02:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0300), '3', 'at night', '03:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0400), '4', 'at night', '04:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0500), '5', 'at night', '05:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0600), '6', 'in the morning', '06:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0700), '7', 'in the morning', '07:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0800), '8', 'in the morning', '08:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d0900), '9', 'in the morning', '09:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1000), '10', 'in the morning', '10:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1100), '11', 'in the morning', '11:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1200), '12', 'noon', '12:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1300), '1', 'in the afternoon', '13:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1400), '2', 'in the afternoon', '14:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1500), '3', 'in the afternoon', '15:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1600), '4', 'in the afternoon', '16:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1700), '5', 'in the afternoon', '17:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1800), '6', 'in the evening', '18:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d1900), '7', 'in the evening', '19:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d2000), '8', 'in the evening', '20:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d2100), '9', 'at night', '21:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d2200), '10', 'at night', '22:00, short-numeric');
+assertPartsNumeric(shortNumeric.formatToParts(d2300), '11', 'at night', '23:00, short-numeric');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/formatToParts.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/formatToParts.js
new file mode 100644
index 0000000000..a0045ba71c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/formatToParts.js
@@ -0,0 +1,21 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Property type and descriptor.
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(
+ typeof Intl.DateTimeFormat.prototype.formatToParts,
+ 'function',
+ '`typeof Intl.DateTimeFormat.prototype.formatToParts` is `function`'
+);
+
+verifyProperty(Intl.DateTimeFormat.prototype, "formatToParts", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js
new file mode 100644
index 0000000000..543dfb2274
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js
@@ -0,0 +1,69 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: Checks basic handling of fractionalSecondDigits.
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+locale: [en-US]
+---*/
+
+const d1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+const d2 = new Date(2019, 7, 10, 1, 2, 3, 567);
+
+function assertParts(parts, minute, second, fractionalSecond, message) {
+ if (fractionalSecond === null) {
+ assert.sameValue(parts.length, 3, `length should be 3, ${message}`);
+ } else {
+ assert.sameValue(parts.length, 5, `length should be 5, ${message}`);
+ }
+ assert.sameValue(parts[0].value, minute, `minute part value. ${message}`);
+ assert.sameValue(parts[0].type, 'minute', `minute part type. ${message}`);
+ assert.sameValue(parts[1].value, ':', `literal part value. ${message}`);
+ assert.sameValue(parts[1].type, 'literal', `literal part type. ${message}`);
+ assert.sameValue(parts[2].value, second, `second part value. ${message}`);
+ assert.sameValue(parts[2].type, 'second', `second part type. ${message}`);
+ if (fractionalSecond !== null) {
+ assert.sameValue(parts[3].value, '.', `literal part value. ${message}`);
+ assert.sameValue(parts[3].type, 'literal', `literal part type. ${message}`);
+ assert.sameValue(parts[4].value, fractionalSecond, `fractionalSecond part value. ${message}`);
+ assert.sameValue(parts[4].type, 'fractionalSecond', `fractionalSecond part type. ${message}`);
+ }
+}
+
+assert.throws(RangeError, () => {
+ new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 0});
+}, "fractionalSecondDigits 0 should throw RangeError for out of range");
+
+assert.throws(RangeError, () => {
+ new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 4});
+}, "fractionalSecondDigits 4 should throw RangeError for out of range");
+
+let dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric"});
+assertParts(dtf.formatToParts(d1), "02", "03", null, "no fractionalSecondDigits round down");
+assertParts(dtf.formatToParts(d2), "02", "03", null, "no fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: undefined});
+assertParts(dtf.formatToParts(d1), "02", "03", null, "no fractionalSecondDigits round down");
+assertParts(dtf.formatToParts(d2), "02", "03", null, "no fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 1});
+assertParts(dtf.formatToParts(d1), "02", "03", "2", "1 fractionalSecondDigits round down");
+assertParts(dtf.formatToParts(d2), "02", "03", "5", "1 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 2});
+assertParts(dtf.formatToParts(d1), "02", "03", "23", "2 fractionalSecondDigits round down");
+assertParts(dtf.formatToParts(d2), "02", "03", "56", "2 fractionalSecondDigits round down");
+
+dtf = new Intl.DateTimeFormat(
+ 'en', { minute: "numeric", second: "numeric", fractionalSecondDigits: 3});
+assertParts(dtf.formatToParts(d1), "02", "03", "234", "3 fractionalSecondDigits round down");
+assertParts(dtf.formatToParts(d2), "02", "03", "567", "3 fractionalSecondDigits round down");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/length.js
new file mode 100644
index 0000000000..cc596ada97
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/length.js
@@ -0,0 +1,15 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatToParts.length.
+includes: [propertyHelper.js]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatToParts, 'length', {
+ value: 1,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/main.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/main.js
new file mode 100644
index 0000000000..b42b917232
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/main.js
@@ -0,0 +1,76 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Tests for existance and behavior of Intl.DateTimeFormat.prototype.formatToParts
+features: [Array.prototype.includes]
+---*/
+
+function reduce(parts) {
+ return parts.map(part => part.value).join('');
+}
+
+function compareFTPtoFormat(locales, options, value) {
+ const dtf = new Intl.DateTimeFormat(locales, options);
+ assert.sameValue(
+ dtf.format(value),
+ reduce(dtf.formatToParts(value)),
+ `Expected the same value for value ${value},
+ locales: ${locales} and options: ${options}`
+ );
+}
+
+compareFTPtoFormat();
+compareFTPtoFormat('pl');
+compareFTPtoFormat(['pl']);
+compareFTPtoFormat([]);
+compareFTPtoFormat(['de'], undefined, 0);
+compareFTPtoFormat(['de'], undefined, -10);
+compareFTPtoFormat(['de'], undefined, 25324234235);
+compareFTPtoFormat(['de'], {
+ day: '2-digit'
+}, Date.now());
+compareFTPtoFormat(['de'], {
+ day: 'numeric',
+ year: '2-digit'
+}, Date.now());
+compareFTPtoFormat(['ar'], {
+ month: 'numeric',
+ day: 'numeric',
+ year: '2-digit'
+}, Date.now());
+
+const actualPartTypes = new Intl.DateTimeFormat('en-us', {
+ weekday: 'long',
+ era: 'long',
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ hour12: true,
+ timeZone: 'UTC',
+ timeZoneName: 'long'
+}).formatToParts(Date.UTC(2012, 11, 17, 3, 0, 42))
+ .map(part => part.type);
+
+const legalPartTypes = [
+ 'weekday',
+ 'era',
+ 'year',
+ 'month',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ 'literal',
+ 'dayPeriod',
+ 'timeZoneName',
+];
+
+actualPartTypes.forEach(function(type) {
+ assert(legalPartTypes.includes(type), `${type} is not a legal type`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/name.js
new file mode 100644
index 0000000000..92b4805ee9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/name.js
@@ -0,0 +1,15 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Intl.DateTimeFormat.prototype.formatToParts.name value and descriptor.
+includes: [propertyHelper.js]
+---*/
+verifyProperty(Intl.DateTimeFormat.prototype.formatToParts, 'name', {
+ value: 'formatToParts',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js
new file mode 100644
index 0000000000..1ebffa9a77
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js
@@ -0,0 +1,30 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that formatted hour and minute are correct for offset time zones.
+---*/
+let date = new Date('1995-12-17T03:24:56Z');
+let tests = {
+ '+0301': {hour: "6", minute: "25"},
+ '+1412': {hour: "5", minute: "36"},
+ '+02': {hour: "5", minute: "24"},
+ '+13:49': {hour: "5", minute: "13"},
+ '-07:56': {hour: "7", minute: "28"},
+ '-12': {hour: "3", minute: "24"},
+ '−0914': {hour: "6", minute: "10"},
+ '−10:03': {hour: "5", minute: "21"},
+ '−0509': {hour: "10", minute: "15"},
+};
+Object.entries(tests).forEach(([timeZone, expected]) => {
+ let df = new Intl.DateTimeFormat("en",
+ {timeZone, timeStyle: "short"});
+ let res = df.formatToParts(date);
+ let hour = res.filter((t) => t.type === "hour")[0].value
+ let minute = res.filter((t) => t.type === "minute")[0].value
+ assert.sameValue(hour, expected.hour, `hour in ${timeZone} time zone:`);
+ assert.sameValue(minute, expected.minute, `minute in ${timeZone} time zone:`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/pattern-on-calendar.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/pattern-on-calendar.js
new file mode 100644
index 0000000000..7a9a753983
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/pattern-on-calendar.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Checks the DateTimeFormat choose different patterns based
+ on calendar.
+includes: [testIntl.js]
+locale: [en]
+---*/
+
+let calendars = allCalendars();
+let date = new Date();
+
+// serialize parts to a string by considering only the type and literal.
+function serializeTypesAndLiteral(parts) {
+ let types = parts.map(part => {
+ if (part.type == "literal") {
+ return `${part.type}(${part.value})`;
+ }
+ return part.type;
+ });
+ return types.join(":");
+}
+
+let df = new Intl.DateTimeFormat("en");
+let base = serializeTypesAndLiteral(df.formatToParts(date));
+
+const foundDifferentPattern = calendars.some(function(calendar) {
+ let cdf = new Intl.DateTimeFormat("en-u-ca-" + calendar);
+ return base != serializeTypesAndLiteral(cdf.formatToParts(date));
+});
+
+// Expect at least some calendar use different pattern.
+assert.sameValue(foundDifferentPattern, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js
new file mode 100644
index 0000000000..1033404e00
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js
@@ -0,0 +1,35 @@
+// Copyright 2019 Google Inc, Igalia S.L. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: >
+ Checks the output of 'relatedYear' and 'yearName' type, and
+ the choice of pattern based on calendar.
+locale: [zh-u-ca-chinese]
+---*/
+
+const df = new Intl.DateTimeFormat("zh-u-ca-chinese", {year: "numeric"});
+const date = new Date(2019, 5, 1);
+const actual = df.formatToParts(date);
+
+const expected = [
+ {type: "relatedYear", value: "2019"},
+ {type: "yearName", value: "己亥"},
+ {type: "literal", value: "年"},
+];
+
+assert.sameValue(Array.isArray(actual), true, 'actual is Array');
+
+if (actual.length <= 2) {
+ expected.shift(); // removes the relatedYear
+}
+
+actual.forEach(({ type, value }, i) => {
+ const { type: eType, value: eValue } = expected[i];
+ assert.sameValue(type, eType, `actual[${i}].type should be ${eType}`);
+ assert.sameValue(value, eValue, `actual[${i}].value should be ${eValue}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year.js
new file mode 100644
index 0000000000..92aae9e730
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/related-year.js
@@ -0,0 +1,23 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: >
+ Checks the output of 'relatedYear' and 'yearName' type, and
+ the choose of pattern base on calendar.
+locale: [en-u-ca-chinese]
+---*/
+
+let df = new Intl.DateTimeFormat("en-u-ca-chinese", {year: "numeric"});
+let parts = df.formatToParts(new Date());
+var relatedYearCount = 0;
+var yearNameCount = 0;
+parts.forEach(function(part) {
+ relatedYearCount += (part.type == "relatedYear") ? 1 : 0;
+ yearNameCount += (part.type == "yearName") ? 1 : 0;
+});
+assert.sameValue(relatedYearCount > 0, true);
+assert.sameValue(yearNameCount > 0, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/return-abrupt-tonumber-date.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/return-abrupt-tonumber-date.js
new file mode 100644
index 0000000000..b8141a3776
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/return-abrupt-tonumber-date.js
@@ -0,0 +1,44 @@
+// Copyright 2016 Leonardo Balter. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Return abrupt completions from ToNumber(date)
+info: |
+ Intl.DateTimeFormat.prototype.formatToParts ([ date ])
+
+ 4. If _date_ is not provided or is *undefined*, then
+ a. Let _x_ be *%Date_now%*().
+ 5. Else,
+ a. Let _x_ be ? ToNumber(_date_).
+features: [Symbol]
+---*/
+
+var obj1 = {
+ valueOf: function() {
+ throw new Test262Error();
+ }
+};
+
+var obj2 = {
+ toString: function() {
+ throw new Test262Error();
+ }
+};
+
+var dtf = new Intl.DateTimeFormat(["pt-BR"]);
+
+assert.throws(Test262Error, function() {
+ dtf.formatToParts(obj1);
+}, "valueOf");
+
+assert.throws(Test262Error, function() {
+ dtf.formatToParts(obj2);
+}, "toString");
+
+var s = Symbol('1');
+assert.throws(TypeError, function() {
+ dtf.formatToParts(s);
+}, "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/shell.js
new file mode 100644
index 0000000000..cce6b3c2a3
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/shell.js
@@ -0,0 +1,360 @@
+// GENERATED, DO NOT EDIT
+// file: dateConstants.js
+// Copyright (C) 2009 the Sputnik authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Collection of date-centric values
+defines:
+ - date_1899_end
+ - date_1900_start
+ - date_1969_end
+ - date_1970_start
+ - date_1999_end
+ - date_2000_start
+ - date_2099_end
+ - date_2100_start
+ - start_of_time
+ - end_of_time
+---*/
+
+var date_1899_end = -2208988800001;
+var date_1900_start = -2208988800000;
+var date_1969_end = -1;
+var date_1970_start = 0;
+var date_1999_end = 946684799999;
+var date_2000_start = 946684800000;
+var date_2099_end = 4102444799999;
+var date_2100_start = 4102444800000;
+
+var start_of_time = -8.64e15;
+var end_of_time = 8.64e15;
+
+// file: deepEqual.js
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: >
+ Compare two values structurally
+defines: [assert.deepEqual]
+---*/
+
+assert.deepEqual = function(actual, expected, message) {
+ var format = assert.deepEqual.format;
+ assert(
+ assert.deepEqual._compare(actual, expected),
+ `Expected ${format(actual)} to be structurally equal to ${format(expected)}. ${(message || '')}`
+ );
+};
+
+assert.deepEqual.format = function(value, seen) {
+ switch (typeof value) {
+ case 'string':
+ return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
+ case 'number':
+ case 'boolean':
+ case 'symbol':
+ case 'bigint':
+ return value.toString();
+ case 'undefined':
+ return 'undefined';
+ case 'function':
+ return `[Function${value.name ? `: ${value.name}` : ''}]`;
+ case 'object':
+ if (value === null) return 'null';
+ if (value instanceof Date) return `Date "${value.toISOString()}"`;
+ if (value instanceof RegExp) return value.toString();
+ if (!seen) {
+ seen = {
+ counter: 0,
+ map: new Map()
+ };
+ }
+
+ let usage = seen.map.get(value);
+ if (usage) {
+ usage.used = true;
+ return `[Ref: #${usage.id}]`;
+ }
+
+ usage = { id: ++seen.counter, used: false };
+ seen.map.set(value, usage);
+
+ if (typeof Set !== "undefined" && value instanceof Set) {
+ return `Set {${Array.from(value).map(value => assert.deepEqual.format(value, seen)).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ if (typeof Map !== "undefined" && value instanceof Map) {
+ return `Map {${Array.from(value).map(pair => `${assert.deepEqual.format(pair[0], seen)} => ${assert.deepEqual.format(pair[1], seen)}}`).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
+ return `[${value.map(value => assert.deepEqual.format(value, seen)).join(', ')}]${usage.used ? ` as #${usage.id}` : ''}`;
+ }
+ let tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
+ if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
+ tag = '[Object: null prototype]';
+ }
+ return `${tag ? `${tag} ` : ''}{ ${Object.keys(value).map(key => `${key.toString()}: ${assert.deepEqual.format(value[key], seen)}`).join(', ')} }${usage.used ? ` as #${usage.id}` : ''}`;
+ default:
+ return typeof value;
+ }
+};
+
+assert.deepEqual._compare = (function () {
+ var EQUAL = 1;
+ var NOT_EQUAL = -1;
+ var UNKNOWN = 0;
+
+ function deepEqual(a, b) {
+ return compareEquality(a, b) === EQUAL;
+ }
+
+ function compareEquality(a, b, cache) {
+ return compareIf(a, b, isOptional, compareOptionality)
+ || compareIf(a, b, isPrimitiveEquatable, comparePrimitiveEquality)
+ || compareIf(a, b, isObjectEquatable, compareObjectEquality, cache)
+ || NOT_EQUAL;
+ }
+
+ function compareIf(a, b, test, compare, cache) {
+ return !test(a)
+ ? !test(b) ? UNKNOWN : NOT_EQUAL
+ : !test(b) ? NOT_EQUAL : cacheComparison(a, b, compare, cache);
+ }
+
+ function tryCompareStrictEquality(a, b) {
+ return a === b ? EQUAL : UNKNOWN;
+ }
+
+ function tryCompareTypeOfEquality(a, b) {
+ return typeof a !== typeof b ? NOT_EQUAL : UNKNOWN;
+ }
+
+ function tryCompareToStringTagEquality(a, b) {
+ var aTag = Symbol.toStringTag in a ? a[Symbol.toStringTag] : undefined;
+ var bTag = Symbol.toStringTag in b ? b[Symbol.toStringTag] : undefined;
+ return aTag !== bTag ? NOT_EQUAL : UNKNOWN;
+ }
+
+ function isOptional(value) {
+ return value === undefined
+ || value === null;
+ }
+
+ function compareOptionality(a, b) {
+ return tryCompareStrictEquality(a, b)
+ || NOT_EQUAL;
+ }
+
+ function isPrimitiveEquatable(value) {
+ switch (typeof value) {
+ case 'string':
+ case 'number':
+ case 'bigint':
+ case 'boolean':
+ case 'symbol':
+ return true;
+ default:
+ return isBoxed(value);
+ }
+ }
+
+ function comparePrimitiveEquality(a, b) {
+ if (isBoxed(a)) a = a.valueOf();
+ if (isBoxed(b)) b = b.valueOf();
+ return tryCompareStrictEquality(a, b)
+ || tryCompareTypeOfEquality(a, b)
+ || compareIf(a, b, isNaNEquatable, compareNaNEquality)
+ || NOT_EQUAL;
+ }
+
+ function isNaNEquatable(value) {
+ return typeof value === 'number';
+ }
+
+ function compareNaNEquality(a, b) {
+ return isNaN(a) && isNaN(b) ? EQUAL : NOT_EQUAL;
+ }
+
+ function isObjectEquatable(value) {
+ return typeof value === 'object';
+ }
+
+ function compareObjectEquality(a, b, cache) {
+ if (!cache) cache = new Map();
+ return getCache(cache, a, b)
+ || setCache(cache, a, b, EQUAL) // consider equal for now
+ || cacheComparison(a, b, tryCompareStrictEquality, cache)
+ || cacheComparison(a, b, tryCompareToStringTagEquality, cache)
+ || compareIf(a, b, isValueOfEquatable, compareValueOfEquality)
+ || compareIf(a, b, isToStringEquatable, compareToStringEquality)
+ || compareIf(a, b, isArrayLikeEquatable, compareArrayLikeEquality, cache)
+ || compareIf(a, b, isStructurallyEquatable, compareStructuralEquality, cache)
+ || compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || cacheComparison(a, b, fail, cache);
+ }
+
+ function isBoxed(value) {
+ return value instanceof String
+ || value instanceof Number
+ || value instanceof Boolean
+ || typeof Symbol === 'function' && value instanceof Symbol
+ || typeof BigInt === 'function' && value instanceof BigInt;
+ }
+
+ function isValueOfEquatable(value) {
+ return value instanceof Date;
+ }
+
+ function compareValueOfEquality(a, b) {
+ return compareIf(a.valueOf(), b.valueOf(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ function isToStringEquatable(value) {
+ return value instanceof RegExp;
+ }
+
+ function compareToStringEquality(a, b) {
+ return compareIf(a.toString(), b.toString(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ function isArrayLikeEquatable(value) {
+ return (Array.isArray ? Array.isArray(value) : value instanceof Array)
+ || (typeof Uint8Array === 'function' && value instanceof Uint8Array)
+ || (typeof Uint8ClampedArray === 'function' && value instanceof Uint8ClampedArray)
+ || (typeof Uint16Array === 'function' && value instanceof Uint16Array)
+ || (typeof Uint32Array === 'function' && value instanceof Uint32Array)
+ || (typeof Int8Array === 'function' && value instanceof Int8Array)
+ || (typeof Int16Array === 'function' && value instanceof Int16Array)
+ || (typeof Int32Array === 'function' && value instanceof Int32Array)
+ || (typeof Float32Array === 'function' && value instanceof Float32Array)
+ || (typeof Float64Array === 'function' && value instanceof Float64Array)
+ || (typeof BigUint64Array === 'function' && value instanceof BigUint64Array)
+ || (typeof BigInt64Array === 'function' && value instanceof BigInt64Array);
+ }
+
+ function compareArrayLikeEquality(a, b, cache) {
+ if (a.length !== b.length) return NOT_EQUAL;
+ for (var i = 0; i < a.length; i++) {
+ if (compareEquality(a[i], b[i], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+ return EQUAL;
+ }
+
+ function isStructurallyEquatable(value) {
+ return !(typeof Promise === 'function' && value instanceof Promise // only comparable by reference
+ || typeof WeakMap === 'function' && value instanceof WeakMap // only comparable by reference
+ || typeof WeakSet === 'function' && value instanceof WeakSet // only comparable by reference
+ || typeof Map === 'function' && value instanceof Map // comparable via @@iterator
+ || typeof Set === 'function' && value instanceof Set); // comparable via @@iterator
+ }
+
+ function compareStructuralEquality(a, b, cache) {
+ var aKeys = [];
+ for (var key in a) aKeys.push(key);
+
+ var bKeys = [];
+ for (var key in b) bKeys.push(key);
+
+ if (aKeys.length !== bKeys.length) {
+ return NOT_EQUAL;
+ }
+
+ aKeys.sort();
+ bKeys.sort();
+
+ for (var i = 0; i < aKeys.length; i++) {
+ var aKey = aKeys[i];
+ var bKey = bKeys[i];
+ if (compareEquality(aKey, bKey, cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ if (compareEquality(a[aKey], b[bKey], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+
+ return compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || EQUAL;
+ }
+
+ function isIterableEquatable(value) {
+ return typeof Symbol === 'function'
+ && typeof value[Symbol.iterator] === 'function';
+ }
+
+ function compareIteratorEquality(a, b, cache) {
+ if (typeof Map === 'function' && a instanceof Map && b instanceof Map ||
+ typeof Set === 'function' && a instanceof Set && b instanceof Set) {
+ if (a.size !== b.size) return NOT_EQUAL; // exit early if we detect a difference in size
+ }
+
+ var ar, br;
+ while (true) {
+ ar = a.next();
+ br = b.next();
+ if (ar.done) {
+ if (br.done) return EQUAL;
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ if (br.done) {
+ if (a.return) a.return();
+ return NOT_EQUAL;
+ }
+ if (compareEquality(ar.value, br.value, cache) === NOT_EQUAL) {
+ if (a.return) a.return();
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ }
+ }
+
+ function compareIterableEquality(a, b, cache) {
+ return compareIteratorEquality(a[Symbol.iterator](), b[Symbol.iterator](), cache);
+ }
+
+ function cacheComparison(a, b, compare, cache) {
+ var result = compare(a, b, cache);
+ if (cache && (result === EQUAL || result === NOT_EQUAL)) {
+ setCache(cache, a, b, /** @type {EQUAL | NOT_EQUAL} */(result));
+ }
+ return result;
+ }
+
+ function fail() {
+ return NOT_EQUAL;
+ }
+
+ function setCache(cache, left, right, result) {
+ var otherCache;
+
+ otherCache = cache.get(left);
+ if (!otherCache) cache.set(left, otherCache = new Map());
+ otherCache.set(right, result);
+
+ otherCache = cache.get(right);
+ if (!otherCache) cache.set(right, otherCache = new Map());
+ otherCache.set(left, result);
+ }
+
+ function getCache(cache, left, right) {
+ var otherCache;
+ var result;
+
+ otherCache = cache.get(left);
+ result = otherCache && otherCache.get(right);
+ if (result) return result;
+
+ otherCache = cache.get(right);
+ result = otherCache && otherCache.get(left);
+ if (result) return result;
+
+ return UNKNOWN;
+ }
+
+ return deepEqual;
+})();
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-resolved-time-zone.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-resolved-time-zone.js
new file mode 100644
index 0000000000..f764995862
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-resolved-time-zone.js
@@ -0,0 +1,107 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.formatToParts
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+includes: [deepEqual.js]
+features: [Temporal]
+---*/
+
+// Tolerate implementation variance by expecting consistency without being prescriptive.
+// TODO: can we change tests to be less reliant on CLDR formats while still testing that
+// Temporal and Intl are behaving as expected?
+const usDayPeriodSpace =
+ new Intl.DateTimeFormat("en-US", { timeStyle: "short" })
+ .formatToParts(0)
+ .find((part, i, parts) => part.type === "literal" && parts[i + 1].type === "dayPeriod")?.value || "";
+
+const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "Pacific/Apia" });
+
+const date = new Temporal.PlainDate(2021, 8, 4);
+const dateResult = formatter.formatToParts(date);
+assert.deepEqual(dateResult, [
+ { type: "month", value: "8" },
+ { type: "literal", value: "/" },
+ { type: "day", value: "4" },
+ { type: "literal", value: "/" },
+ { type: "year", value: "2021" },
+], "plain date");
+
+const datetime1 = new Temporal.PlainDateTime(2021, 8, 4, 0, 30, 45, 123, 456, 789);
+const datetimeResult1 = formatter.formatToParts(datetime1);
+assert.deepEqual(datetimeResult1, [
+ { type: "month", value: "8" },
+ { type: "literal", value: "/" },
+ { type: "day", value: "4" },
+ { type: "literal", value: "/" },
+ { type: "year", value: "2021" },
+ { type: "literal", value: ", " },
+ { type: "hour", value: "12" },
+ { type: "literal", value: ":" },
+ { type: "minute", value: "30" },
+ { type: "literal", value: ":" },
+ { type: "second", value: "45" },
+ { type: "literal", value: usDayPeriodSpace },
+ { type: "dayPeriod", value: "AM" },
+], "plain datetime close to beginning of day");
+const datetime2 = new Temporal.PlainDateTime(2021, 8, 4, 23, 30, 45, 123, 456, 789);
+const datetimeResult2 = formatter.formatToParts(datetime2);
+assert.deepEqual(datetimeResult2, [
+ { type: "month", value: "8" },
+ { type: "literal", value: "/" },
+ { type: "day", value: "4" },
+ { type: "literal", value: "/" },
+ { type: "year", value: "2021" },
+ { type: "literal", value: ", " },
+ { type: "hour", value: "11" },
+ { type: "literal", value: ":" },
+ { type: "minute", value: "30" },
+ { type: "literal", value: ":" },
+ { type: "second", value: "45" },
+ { type: "literal", value: usDayPeriodSpace },
+ { type: "dayPeriod", value: "PM" },
+], "plain datetime close to end of day");
+
+const monthDay = new Temporal.PlainMonthDay(8, 4, "gregory");
+const monthDayResult = formatter.formatToParts(monthDay);
+assert.deepEqual(monthDayResult, [
+ { type: "month", value: "8" },
+ { type: "literal", value: "/" },
+ { type: "day", value: "4" },
+], "plain month-day");
+
+const time1 = new Temporal.PlainTime(0, 30, 45, 123, 456, 789);
+const timeResult1 = formatter.formatToParts(time1);
+assert.deepEqual(timeResult1, [
+ { type: "hour", value: "12" },
+ { type: "literal", value: ":" },
+ { type: "minute", value: "30" },
+ { type: "literal", value: ":" },
+ { type: "second", value: "45" },
+ { type: "literal", value: usDayPeriodSpace },
+ { type: "dayPeriod", value: "AM" },
+], "plain time close to beginning of day");
+const time2 = new Temporal.PlainTime(23, 30, 45, 123, 456, 789);
+const timeResult2 = formatter.formatToParts(time2);
+assert.deepEqual(timeResult2, [
+ { type: "hour", value: "11" },
+ { type: "literal", value: ":" },
+ { type: "minute", value: "30" },
+ { type: "literal", value: ":" },
+ { type: "second", value: "45" },
+ { type: "literal", value: usDayPeriodSpace },
+ { type: "dayPeriod", value: "PM" },
+], "plain time close to end of day");
+
+const month = new Temporal.PlainYearMonth(2021, 8, "gregory");
+const monthResult = formatter.formatToParts(month);
+assert.deepEqual(monthResult, [
+ { type: "month", value: "8" },
+ { type: "literal", value: "/" },
+ { type: "year", value: "2021" },
+], "plain year-month");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-zoneddatetime-not-supported.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-zoneddatetime-not-supported.js
new file mode 100644
index 0000000000..ec7438195b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/temporal-zoneddatetime-not-supported.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.formatToParts
+description: Temporal.ZonedDateTime is not supported directly in formatToParts()
+features: [Temporal]
+---*/
+
+const formatter = new Intl.DateTimeFormat();
+
+// Check that TypeError would not be thrown for a different reason
+const {timeZone, ...options} = formatter.resolvedOptions();
+const datetime = new Temporal.ZonedDateTime(0n, timeZone);
+assert.sameValue(typeof datetime.toLocaleString(undefined, options), "string", "toLocaleString() with same options succeeds");
+
+assert.throws(TypeError, () => formatter.formatToParts(datetime), "formatToParts() does not support Temporal.ZonedDateTime");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-has-not-internal-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-has-not-internal-throws.js
new file mode 100644
index 0000000000..18dae83a16
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-has-not-internal-throws.js
@@ -0,0 +1,19 @@
+// Copyright 2016 Leonardo Balter. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: >
+ Throws a TypeError if this is not a DateTimeFormat object
+---*/
+
+var formatToParts = Intl.DateTimeFormat.prototype.formatToParts;
+
+assert.throws(TypeError, function() {
+ formatToParts.call({});
+}, "{}");
+
+assert.throws(TypeError, function() {
+ formatToParts.call(new Date());
+}, "new Date()");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-is-not-object-throws.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-is-not-object-throws.js
new file mode 100644
index 0000000000..0837bfb1ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/this-is-not-object-throws.js
@@ -0,0 +1,40 @@
+// Copyright 2016 Leonardo Balter. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Throws a TypeError if this is not Object
+features: [Symbol]
+---*/
+
+var formatToParts = Intl.DateTimeFormat.prototype.formatToParts;
+
+assert.throws(TypeError, function() {
+ formatToParts.call(undefined);
+}, "undefined");
+
+assert.throws(TypeError, function() {
+ formatToParts.call(null);
+}, "null");
+
+assert.throws(TypeError, function() {
+ formatToParts.call(42);
+}, "number");
+
+assert.throws(TypeError, function() {
+ formatToParts.call("foo");
+}, "string");
+
+assert.throws(TypeError, function() {
+ formatToParts.call(false);
+}, "false");
+
+assert.throws(TypeError, function() {
+ formatToParts.call(true);
+}, "true");
+
+var s = Symbol('1');
+assert.throws(TypeError, function() {
+ formatToParts.call(s);
+}, "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-near-time-boundaries.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-near-time-boundaries.js
new file mode 100644
index 0000000000..61599ebbc1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-near-time-boundaries.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ TimeClip is applied when calling Intl.DateTimeFormat.prototype.formatToParts.
+info: >
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. If x is NaN, throw a RangeError exception.
+ 3. ...
+
+ 20.3.1.15 TimeClip ( time )
+ ...
+ 2. If abs(time) > 8.64 × 10^15, return NaN.
+ ...
+
+includes: [dateConstants.js]
+---*/
+
+var dtf = new Intl.DateTimeFormat();
+
+// Test values near the start of the ECMAScript time range.
+assert.throws(RangeError, function() {
+ dtf.formatToParts(start_of_time - 1);
+});
+assert.sameValue(typeof dtf.formatToParts(start_of_time), "object");
+assert.sameValue(typeof dtf.formatToParts(start_of_time + 1), "object");
+
+// Test values near the end of the ECMAScript time range.
+assert.sameValue(typeof dtf.formatToParts(end_of_time - 1), "object");
+assert.sameValue(typeof dtf.formatToParts(end_of_time), "object");
+assert.throws(RangeError, function() {
+ dtf.formatToParts(end_of_time + 1);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-to-integer.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-to-integer.js
new file mode 100644
index 0000000000..576b08ca92
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/formatToParts/time-clip-to-integer.js
@@ -0,0 +1,43 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondatetimepattern
+description: |
+ TimeClip applies ToInteger on its input value.
+info: >
+ 12.1.6 PartitionDateTimePattern ( dateTimeFormat, x )
+
+ 1. Let x be TimeClip(x).
+ 2. ...
+
+ 20.3.1.15 TimeClip ( time )
+ ...
+ 3. Let clippedTime be ! ToInteger(time).
+ 4. If clippedTime is -0, set clippedTime to +0.
+ 5. Return clippedTime.
+---*/
+
+// Switch to a time format instead of using DateTimeFormat's default date-only format.
+var dtf = new Intl.DateTimeFormat(undefined, {
+ hour: "numeric", minute: "numeric", second: "numeric"
+});
+
+function formatAsString(dtf, time) {
+ return dtf.formatToParts(time).map(part => part.value).join("");
+}
+
+var expected = formatAsString(dtf, 0);
+
+assert.sameValue(formatAsString(dtf, -0.9), expected, "formatToParts(-0.9)");
+assert.sameValue(formatAsString(dtf, -0.5), expected, "formatToParts(-0.5)");
+assert.sameValue(formatAsString(dtf, -0.1), expected, "formatToParts(-0.1)");
+assert.sameValue(formatAsString(dtf, -Number.MIN_VALUE), expected, "formatToParts(-Number.MIN_VALUE)");
+assert.sameValue(formatAsString(dtf, -0), expected, "formatToParts(-0)");
+assert.sameValue(formatAsString(dtf, +0), expected, "formatToParts(+0)");
+assert.sameValue(formatAsString(dtf, Number.MIN_VALUE), expected, "formatToParts(Number.MIN_VALUE)");
+assert.sameValue(formatAsString(dtf, 0.1), expected, "formatToParts(0.1)");
+assert.sameValue(formatAsString(dtf, 0.5), expected, "formatToParts(0.5)");
+assert.sameValue(formatAsString(dtf, 0.9), expected, "formatToParts(0.9)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/prop-desc.js
new file mode 100644
index 0000000000..711e47059a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/prop-desc.js
@@ -0,0 +1,19 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.2.1
+description: >
+ Tests that Intl.DateTimeFormat.prototype has the required
+ attributes.
+author: Norbert Lindenberg
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/basic.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/basic.js
new file mode 100644
index 0000000000..4ca918bb63
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/basic.js
@@ -0,0 +1,53 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.3.3
+description: >
+ Tests that the object returned by
+ Intl.DateTimeFormat.prototype.resolvedOptions has the right
+ properties.
+author: Norbert Lindenberg
+includes: [testIntl.js, propertyHelper.js]
+---*/
+
+var actual = new Intl.DateTimeFormat().resolvedOptions();
+
+var actual2 = new Intl.DateTimeFormat().resolvedOptions();
+assert.notSameValue(actual2, actual, "resolvedOptions returned the same object twice.");
+
+var calendars = allCalendars();
+
+// this assumes the default values where the specification provides them
+assert(isCanonicalizedStructurallyValidLanguageTag(actual.locale),
+ "Invalid locale: " + actual.locale);
+assert.notSameValue(calendars.indexOf(actual.calendar), -1,
+ "Invalid calendar: " + actual.calendar);
+assert(isValidNumberingSystem(actual.numberingSystem),
+ "Invalid numbering system: " + actual.numberingSystem);
+assert(isCanonicalizedStructurallyValidTimeZoneName(actual.timeZone),
+ "Invalid time zone: " + actual.timeZone);
+assert.notSameValue(["2-digit", "numeric"].indexOf(actual.year), -1,
+ "Invalid year: " + actual.year);
+assert.notSameValue(["2-digit", "numeric", "narrow", "short", "long"].indexOf(actual.month), -1,
+ "Invalid month: " + actual.month);
+assert.notSameValue(["2-digit", "numeric"].indexOf(actual.day), -1,
+ "Invalid day: " + actual.day);
+
+var dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+verifyProperty(actual, "locale", dataPropertyDesc);
+verifyProperty(actual, "calendar", dataPropertyDesc);
+verifyProperty(actual, "numberingSystem", dataPropertyDesc);
+verifyProperty(actual, "timeZone", dataPropertyDesc);
+verifyProperty(actual, "weekday", undefined);
+verifyProperty(actual, "era", undefined);
+verifyProperty(actual, "year", dataPropertyDesc);
+verifyProperty(actual, "month", dataPropertyDesc);
+verifyProperty(actual, "day", dataPropertyDesc);
+verifyProperty(actual, "hour", undefined);
+verifyProperty(actual, "minute", undefined);
+verifyProperty(actual, "second", undefined);
+verifyProperty(actual, "timeZoneName", undefined);
+verifyProperty(actual, "hour12", undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js
new file mode 100644
index 0000000000..643376e7e2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.3.3_L15
+description: >
+ Tests that Intl.DateTimeFormat.prototype.resolvedOptions meets
+ the requirements for built-in objects defined by the introduction
+ of chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat.prototype.resolvedOptions), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.DateTimeFormat.prototype.resolvedOptions),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.DateTimeFormat.prototype.resolvedOptions), Function.prototype);
+
+assert.sameValue(Intl.DateTimeFormat.prototype.resolvedOptions.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.DateTimeFormat.prototype.resolvedOptions), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-dateStyle.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-dateStyle.js
new file mode 100644
index 0000000000..ebab7d4ad9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-dateStyle.js
@@ -0,0 +1,93 @@
+// Copyright 2019 Mozilla Corporation, Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions properly
+ reflect hourCycle settings when using dateStyle.
+ Note: "properly reflect hourCycle settings when using dateStyle", in this context, means "if dateStyle but not timeStyle is set, both hourCycle and hour12 will be *undefined*". This is because the CreateDateTimeFormat AO resets [[HourCycle]] to *undefined* if [[Hour]] is *undefined*, and if dateStyle but not timeStyle is set, [[HourCycle]] is set to *undefined*.
+info: |
+ 11.3.7 Intl.DateTimeFormat.prototype.resolvedOptions()
+ ...
+ 5. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. If p is "hour12", then
+ i. Let hc be dtf.[[HourCycle]].
+ ii. If hc is "h11" or "h12", let v be true.
+ iii. Else if, hc is "h23" or "h24", let v be false.
+ iv. Else, let v be undefined.
+ c. Else,
+ i. Let v be the value of dtf's internal slot whose name is the Internal Slot value of the current row.
+ d. If the Internal Slot value of the current row is an Internal Slot value in Table 7, then
+ i. If dtf.[[DateStyle]] is not undefined or dtf.[[TimeStyle]] is not undefined, then
+ 1. Let v be undefined.
+ e. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+
+ 11.1.2 CreateDateTimeFormat( newTarget, locales, options, required, defaults)
+ ...
+ 45. If dateTimeFormat.[[Hour]] is undefined, then
+
+ a. Set dateTimeFormat.[[HourCycle]] to undefined.
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+const hcValues = ["h11", "h12", "h23", "h24"];
+const hour12Values = ["h11", "h12"];
+
+for (const dateStyle of ["full", "long", "medium", "short"]) {
+ assert.sameValue(new Intl.DateTimeFormat([], { dateStyle }).resolvedOptions().dateStyle,
+ dateStyle,
+ `Should support dateStyle=${dateStyle}`);
+
+ /* Values passed via unicode extension key set to *undefined* */
+
+ for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat(`de-u-hc-${hcValue}`, {
+ dateStyle,
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, undefined);
+ assert.sameValue(resolvedOptions.hour12, undefined);
+ }
+
+ /* Values passed via options set to *undefined**/
+
+ for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat("en-US", {
+ dateStyle,
+ hourCycle: hcValue
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, undefined);
+ assert.sameValue(resolvedOptions.hour12, undefined);
+ }
+
+ let resolvedOptions = new Intl.DateTimeFormat("en-US-u-hc-h12", {
+ dateStyle,
+ hourCycle: "h23"
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, undefined);
+ assert.sameValue(resolvedOptions.hour12, undefined);
+
+ resolvedOptions = new Intl.DateTimeFormat("fr", {
+ dateStyle,
+ hour12: true,
+ hourCycle: "h23"
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, undefined);
+ assert.sameValue(resolvedOptions.hour12, undefined);
+
+ resolvedOptions = new Intl.DateTimeFormat("fr-u-hc-h24", {
+ dateStyle,
+ hour12: true,
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, undefined);
+ assert.sameValue(resolvedOptions.hour12, undefined);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js
new file mode 100644
index 0000000000..9063e045a6
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Google Inc., 2023 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions properly
+ reflect hourCycle settings.
+info: |
+ 11.3.7 Intl.DateTimeFormat.prototype.resolvedOptions()
+
+ 11.1.2 CreateDateTimeFormat ( dateTimeFormat, locales, options, required, defaults )
+ 23. Let dataLocaleData be localeData.[[<dataLocale>]].
+ 24. If hour12 is true, then
+ a. Let hc be dataLocaleData.[[hourCycle12]].
+ 25. Else if hour12 is false, then
+ a. Let hc be dataLocaleData.[[hourCycle24]].
+ 26. Else,
+ a. Assert: hour12 is undefined.
+ b. Let hc be r.[[hc]].
+ c. If hc is null, set hc to dataLocaleData.[[hourCycle]].
+ 27. Set dateTimeFormat.[[HourCycle]] to hc.
+
+locale: [en, fr, it, ja, zh, ko, ar, hi, en-u-hc-h24]
+---*/
+
+let locales = ["en", "fr", "it", "ja", "ja-u-hc-h11", "zh", "ko", "ar", "hi", "en-u-hc-h24"];
+
+locales.forEach(function(locale) {
+ let hcDefault = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions().hourCycle;
+ if (hcDefault === "h11" || hcDefault === "h12") {
+ assert.sameValue(new Intl.DateTimeFormat(locale, { hour: "numeric", hour12: true }).resolvedOptions().hourCycle, hcDefault);
+
+ // no locale has "h24" as a default. see https://github.com/tc39/ecma402/pull/758#issue-1622377292
+ assert.sameValue(new Intl.DateTimeFormat(locale, { hour: "numeric", hour12: false }).resolvedOptions().hourCycle, "h23");
+ }
+
+ // however, "h24" can be set via locale extension.
+ if (hcDefault === "h23" || hcDefault === "h24") {
+ assert.sameValue(new Intl.DateTimeFormat(locale, { hour: "numeric", hour12: false }).resolvedOptions().hourCycle, hcDefault);
+ }
+
+ let hcHour12 = new Intl.DateTimeFormat(locale, { hour: "numeric", hour12: true }).resolvedOptions().hourCycle;
+ assert(hcHour12 === "h11" || hcHour12 === "h12", "Expected `hourCycle`: " + hcHour12 + " to be in [\"h11\", \"h12\"]");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-timeStyle.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-timeStyle.js
new file mode 100644
index 0000000000..e471fbdc60
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-timeStyle.js
@@ -0,0 +1,89 @@
+// Copyright 2019 Mozilla Corporation, Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions properly
+ reflect hourCycle settings when using timeStyle.
+includes: [propertyHelper.js]
+features: [Intl.DateTimeFormat-datetimestyle, Array.prototype.includes]
+---*/
+
+const hcValues = ["h11", "h12", "h23", "h24"];
+const hour12Values = ["h11", "h12"];
+const dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+
+for (const timeStyle of ["full", "long", "medium", "short"]) {
+ assert.sameValue(new Intl.DateTimeFormat([], { timeStyle }).resolvedOptions().timeStyle,
+ timeStyle,
+ `Should support timeStyle=${timeStyle}`);
+
+ /* Values passed via unicode extension key work */
+
+ for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat(`de-u-hc-${hcValue}`, {
+ timeStyle,
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, hcValue);
+ assert.sameValue(resolvedOptions.hour12, hour12Values.includes(hcValue));
+ }
+
+ /* Values passed via options work */
+
+ for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat("en-US", {
+ timeStyle,
+ hourCycle: hcValue
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, hcValue);
+ assert.sameValue(resolvedOptions.hour12, hour12Values.includes(hcValue));
+
+ verifyProperty(resolvedOptions, "hourCycle", dataPropertyDesc);
+ verifyProperty(resolvedOptions, "hour12", dataPropertyDesc);
+ }
+
+ /* When both extension key and option is passed, option takes precedence */
+
+ let resolvedOptions = new Intl.DateTimeFormat("en-US-u-hc-h12", {
+ timeStyle,
+ hourCycle: "h23"
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, "h23");
+ assert.sameValue(resolvedOptions.hour12, false);
+
+ verifyProperty(resolvedOptions, "hourCycle", dataPropertyDesc);
+ verifyProperty(resolvedOptions, "hour12", dataPropertyDesc);
+
+ /* When hour12 and hourCycle are set, hour12 takes precedence */
+
+ resolvedOptions = new Intl.DateTimeFormat("fr", {
+ timeStyle,
+ hour12: true,
+ hourCycle: "h23"
+ }).resolvedOptions();
+
+ assert(hour12Values.includes(resolvedOptions.hourCycle));
+ assert.sameValue(resolvedOptions.hour12, true);
+
+ verifyProperty(resolvedOptions, "hourCycle", dataPropertyDesc);
+ verifyProperty(resolvedOptions, "hour12", dataPropertyDesc);
+
+ /* When hour12 and extension key are set, hour12 takes precedence */
+
+ resolvedOptions = new Intl.DateTimeFormat("fr-u-hc-h24", {
+ timeStyle,
+ hour12: true,
+ }).resolvedOptions();
+
+ assert(hour12Values.includes(resolvedOptions.hourCycle));
+ assert.sameValue(resolvedOptions.hour12, true);
+
+ verifyProperty(resolvedOptions, "hourCycle", dataPropertyDesc);
+ verifyProperty(resolvedOptions, "hour12", dataPropertyDesc);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle.js
new file mode 100644
index 0000000000..c92578e3af
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle.js
@@ -0,0 +1,102 @@
+// Copyright 2017 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions properly
+ reflect hourCycle settings.
+info: |
+ 12.4.5 Intl.DateTimeFormat.prototype.resolvedOptions()
+
+includes: [propertyHelper.js]
+features: [Array.prototype.includes]
+---*/
+
+/* Values passed via unicode extension key work */
+
+const hcValues = ['h11', 'h12', 'h23', 'h24'];
+const hour12Values = ['h11', 'h12'];
+
+const dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+
+for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat(`de-u-hc-${hcValue}`, {
+ hour: 'numeric'
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, hcValue);
+ assert.sameValue(resolvedOptions.hour12, hour12Values.includes(hcValue));
+
+ verifyProperty(resolvedOptions, 'hourCycle', dataPropertyDesc);
+ verifyProperty(resolvedOptions, 'hour12', dataPropertyDesc);
+}
+
+/* Values passed via options work */
+
+for (const hcValue of hcValues) {
+ const resolvedOptions = new Intl.DateTimeFormat(`en-US`, {
+ hour: 'numeric',
+ hourCycle: hcValue
+ }).resolvedOptions();
+
+ assert.sameValue(resolvedOptions.hourCycle, hcValue);
+ assert.sameValue(resolvedOptions.hour12, hour12Values.includes(hcValue));
+
+ verifyProperty(resolvedOptions, 'hourCycle', dataPropertyDesc);
+ verifyProperty(resolvedOptions, 'hour12', dataPropertyDesc);
+}
+
+/* When both extension key and option is passed, option takes precedence */
+
+let resolvedOptions = new Intl.DateTimeFormat(`en-US-u-hc-h12`, {
+ hour: 'numeric',
+ hourCycle: 'h23'
+}).resolvedOptions();
+
+assert.sameValue(resolvedOptions.hourCycle, 'h23');
+assert.sameValue(resolvedOptions.hour12, false);
+
+verifyProperty(resolvedOptions, 'hourCycle', dataPropertyDesc);
+verifyProperty(resolvedOptions, 'hour12', dataPropertyDesc);
+
+/* When hour12 and hourCycle are set, hour12 takes precedence */
+
+resolvedOptions = new Intl.DateTimeFormat(`fr`, {
+ hour: 'numeric',
+ hour12: true,
+ hourCycle: 'h23'
+}).resolvedOptions();
+
+assert(hour12Values.includes(resolvedOptions.hourCycle));
+assert.sameValue(resolvedOptions.hour12, true);
+
+verifyProperty(resolvedOptions, 'hourCycle', dataPropertyDesc);
+verifyProperty(resolvedOptions, 'hour12', dataPropertyDesc);
+
+/* When hour12 and extension key are set, hour12 takes precedence */
+
+resolvedOptions = new Intl.DateTimeFormat(`fr-u-hc-h24`, {
+ hour: 'numeric',
+ hour12: true,
+}).resolvedOptions();
+
+assert(hour12Values.includes(resolvedOptions.hourCycle));
+assert.sameValue(resolvedOptions.hour12, true);
+
+verifyProperty(resolvedOptions, 'hourCycle', dataPropertyDesc);
+verifyProperty(resolvedOptions, 'hour12', dataPropertyDesc);
+
+/* When the hour is not in the pattern, hourCycle and hour12 are not defined. */
+
+resolvedOptions = new Intl.DateTimeFormat("fr", {
+ hourCycle: "h12",
+ hour12: false,
+}).resolvedOptions();
+
+assert.sameValue(resolvedOptions.hour, undefined,
+ "Precondition: hour should not be included by default");
+assert.sameValue(resolvedOptions.hourCycle, undefined);
+assert.sameValue(resolvedOptions.hour12, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..7f0bf43eb6
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions.length is 0.
+info: |
+ Intl.DateTimeFormat.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..33915a9338
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ Intl.DateTimeFormat.prototype.resolvedOptions.name is "resolvedOptions".
+info: |
+ 12.4.4 Intl.DateTimeFormat.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/no-instanceof.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/no-instanceof.js
new file mode 100644
index 0000000000..0e81ebb1be
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/no-instanceof.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: >
+ Tests that Intl.DateTimeFormat.prototype.resolvedOptions calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ UnwrapDateTimeFormat ( dtf )
+ 2. If dtf does not have an [[InitializedDateTimeFormat]] internal slot and
+ ? OrdinaryHasInstance(%DateTimeFormat%, dtf) is true, then
+ a. Return ? Get(dtf, %Intl%.[[FallbackSymbol]]).
+---*/
+
+const dtf = Object.create(Intl.DateTimeFormat.prototype);
+
+Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+assert.throws(TypeError, () => dtf.resolvedOptions());
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js
new file mode 100644
index 0000000000..9f89b6106c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js
@@ -0,0 +1,30 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: Tests that offset time zones are correctly normalized in resolvedOptions() output.
+---*/
+let validOffsetTimeZones = [
+ '+03',
+ '+13',
+ '+23',
+ '-07',
+ '-14',
+ '-21',
+ '+01:03',
+ '+15:59',
+ '+22:27',
+ '-02:32',
+ '-17:01',
+ '-22:23',
+];
+validOffsetTimeZones.forEach((timeZone) => {
+ let df = new Intl.DateTimeFormat(undefined, {timeZone});
+ let expected = timeZone;
+ if (expected.length == 3) {
+ expected += ":00";
+ }
+ assert.sameValue(df.resolvedOptions().timeZone, expected, timeZone);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js
new file mode 100644
index 0000000000..714286e287
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js
@@ -0,0 +1,35 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createdatetimeformat
+description: Tests that offset time zones are correctly normalized in resolvedOptions() output.
+---*/
+let validOffsetTimeZones = {
+ '-00': '+00:00',
+ '-00:00': '+00:00',
+ '−00:00': '+00:00',
+ '+00': '+00:00',
+ '+0000': '+00:00',
+ '+0300': '+03:00',
+ '+03:00': '+03:00',
+ '+13:00': '+13:00',
+ '+2300': '+23:00',
+ '-07:00': '-07:00',
+ '-14': '-14:00',
+ '-2100': '-21:00',
+ '−2200': '-22:00',
+ '+0103': '+01:03',
+ '+15:59': '+15:59',
+ '+2227': '+22:27',
+ '-02:32': '-02:32',
+ '-1701': '-17:01',
+ '-22:23': '-22:23',
+ '−22:53': '-22:53',
+};
+Object.keys(validOffsetTimeZones).forEach((timeZone) => {
+ let df = new Intl.DateTimeFormat(undefined, {timeZone});
+ let expected = validOffsetTimeZones[timeZone];
+ assert.sameValue(df.resolvedOptions().timeZone, expected, timeZone);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-dayPeriod.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-dayPeriod.js
new file mode 100644
index 0000000000..971ba2ac42
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-dayPeriod.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+const options = new Intl.DateTimeFormat([], {
+ "dayPeriod": "short",
+ "hour": "numeric",
+ "minute": "numeric",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "calendar",
+ "numberingSystem",
+ "timeZone",
+ "hourCycle",
+ "hour12",
+ "dayPeriod",
+ "hour",
+ "minute",
+];
+
+let actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-fractionalSecondDigits.js
new file mode 100644
index 0000000000..2fc022b0da
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-fractionalSecondDigits.js
@@ -0,0 +1,36 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+const options = new Intl.DateTimeFormat([], {
+ "fractionalSecondDigits": 3,
+ "minute": "numeric",
+ "second": "numeric",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "calendar",
+ "numberingSystem",
+ "timeZone",
+ "minute",
+ "second",
+ "fractionalSecondDigits",
+];
+
+let actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-style.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-style.js
new file mode 100644
index 0000000000..2546b1b746
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order-style.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.DateTimeFormat-datetimestyle]
+---*/
+
+const options = new Intl.DateTimeFormat([], {
+ "hourCycle": "h24",
+ "weekday": "short",
+ "era": "short",
+ "year": "numeric",
+ "month": "numeric",
+ "day": "numeric",
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "calendar",
+ "numberingSystem",
+ "timeZone",
+ "hourCycle",
+ "hour12",
+];
+
+let actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..13b318c5e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js
@@ -0,0 +1,50 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+---*/
+
+const options = new Intl.DateTimeFormat([], {
+ "hourCycle": "h24",
+ "weekday": "short",
+ "era": "short",
+ "year": "numeric",
+ "month": "numeric",
+ "day": "numeric",
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "calendar",
+ "numberingSystem",
+ "timeZone",
+ "hourCycle",
+ "hour12",
+ "weekday",
+ "era",
+ "year",
+ "month",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "timeZoneName",
+];
+
+let actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..b5918cfa1e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.resolvedoptions
+description: >
+ "resolvedOptions" property of Intl.DateTimeFormat.prototype.
+info: |
+ Intl.DateTimeFormat.prototype.resolvedOptions ()
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/resolved-locale-with-hc-unicode.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/resolved-locale-with-hc-unicode.js
new file mode 100644
index 0000000000..95d5f4c666
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/resolved-locale-with-hc-unicode.js
@@ -0,0 +1,88 @@
+// Copyright 2018 André Bargull. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.prototype.resolvedOptions
+description: >
+ The resolved locale doesn't include a hc Unicode extension value if the
+ hour12 or hourCycle option is also present.
+info: |
+ 11.1.2 CreateDateTimeFormat( dateTimeFormat, locales, options, required, defaults )
+ ...
+ 13. Let hour12 be ? GetOption(options, "hour12", boolean, empty, undefined).
+ 14. Let hourCycle be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined).
+ 15. If hour12 is not undefined, then
+ a. Set hourCycle to null.
+ ...
+
+ 9.2.6 ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData)
+ ...
+ 8. For each element key of relevantExtensionKeys in List order, do
+ ...
+ i. If options has a field [[<key>]], then
+ i. Let optionsValue be options.[[<key>]].
+ ii. Assert: Type(optionsValue) is either String, Undefined, or Null.
+ iii. If keyLocaleData contains optionsValue, then
+ 1. If SameValue(optionsValue, value) is false, then
+ a. Let value be optionsValue.
+ b. Let supportedExtensionAddition be "".
+ ...
+---*/
+
+var defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
+var defaultLocaleWithHourCycle = defaultLocale + "-u-hc-h11";
+
+function assertLocale(locale, expectedLocale, options, message) {
+ var resolved = new Intl.DateTimeFormat(locale, {
+ hour: "2-digit",
+ hour12: options.hour12,
+ hourCycle: options.hourCycle,
+ }).resolvedOptions();
+ assert.sameValue(resolved.locale, expectedLocale, message + " (With hour option.)");
+
+ // Also test the case when no hour option is present at all.
+ // The resolved options don't include hour12 and hourCycle if the date-time
+ // formatter doesn't include an hour option. This restriction doesn't apply
+ // to the hc Unicode extension value.
+ resolved = new Intl.DateTimeFormat(locale, {
+ hour12: options.hour12,
+ hourCycle: options.hourCycle,
+ }).resolvedOptions();
+ assert.sameValue(resolved.locale, expectedLocale, message + " (Without hour option.)");
+}
+
+assertLocale(defaultLocaleWithHourCycle, defaultLocale, {
+ hour12: false,
+ hourCycle: "h23",
+}, "hour12 and hourCycle options and hc Unicode extension value are present.");
+
+assertLocale(defaultLocaleWithHourCycle, defaultLocale, {
+ hour12: false,
+}, "hour12 option and hc Unicode extension value are present.");
+
+assertLocale(defaultLocaleWithHourCycle, defaultLocale, {
+ hourCycle: "h23",
+}, "hourCycle option and hc Unicode extension value are present.");
+
+assertLocale(defaultLocaleWithHourCycle, defaultLocaleWithHourCycle, {
+}, "Only hc Unicode extension value is present.");
+
+// And make sure the hc Unicode extension doesn't get added if it's not present
+// in the requested locale.
+assertLocale(defaultLocale, defaultLocale, {
+ hour12: false,
+ hourCycle: "h23",
+}, "hour12 and hourCycle options are present, but no hc Unicode extension value.");
+
+assertLocale(defaultLocale, defaultLocale, {
+ hour12: false,
+}, "hourCycle option is present, but no hc Unicode extension value.");
+
+assertLocale(defaultLocale, defaultLocale, {
+ hourCycle: "h23",
+}, "hourCycle option is present, but no hc Unicode extension value.");
+
+assertLocale(defaultLocale, defaultLocale, {
+}, "No options are present and no hc Unicode extension value.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/resolvedOptions/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-datetimeformat-prototype.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-datetimeformat-prototype.js
new file mode 100644
index 0000000000..34db44141b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-datetimeformat-prototype.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-datetimeformat-prototype-object
+description: >
+ Tests that Intl.DateTimeFormat.prototype is not an object that has
+ been initialized as an Intl.DateTimeFormat.
+author: Roozbeh Pournader
+---*/
+
+// test by calling a function that should fail as "this" is not an object
+// initialized as an Intl.DateTimeFormat
+assert.throws(TypeError, () => Intl.DateTimeFormat.prototype.format(0),
+ "Intl.DateTimeFormat's prototype is not an object that has been initialized as an Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-not-datetimeformat.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-not-datetimeformat.js
new file mode 100644
index 0000000000..4805ca2f69
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/this-value-not-datetimeformat.js
@@ -0,0 +1,28 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.3_b
+description: >
+ Tests that Intl.DateTimeFormat.prototype functions throw a
+ TypeError if called on a non-object value or an object that hasn't
+ been initialized as a DateTimeFormat.
+author: Norbert Lindenberg
+---*/
+
+var functions = {
+ "format getter": Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get,
+ resolvedOptions: Intl.DateTimeFormat.prototype.resolvedOptions
+};
+var invalidTargets = [undefined, null, true, 0, "DateTimeFormat", [], {}];
+
+Object.getOwnPropertyNames(functions).forEach(function (functionName) {
+ var f = functions[functionName];
+ invalidTargets.forEach(function (target) {
+ assert.throws(TypeError, function() {
+ f.call(target);
+ }, "Calling " + functionName + " on " + target + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-changed-tag.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-changed-tag.js
new file mode 100644
index 0000000000..69b853ad0a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-changed-tag.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype-@@tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.DateTimeFormat.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.DateTimeFormat.prototype [ @@toStringTag ]
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+---*/
+
+Object.defineProperty(Intl.DateTimeFormat.prototype, Symbol.toStringTag, {
+ value: "test262",
+});
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object test262]");
+assert.sameValue(Object.prototype.toString.call(new Intl.DateTimeFormat()), "[object test262]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-removed-tag.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-removed-tag.js
new file mode 100644
index 0000000000..170562a720
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString-removed-tag.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype-@@tostringtag
+description: >
+ Object.prototype.toString doesn't special-case neither Intl.DateTimeFormat instances nor its prototype.
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+features: [Symbol.toStringTag]
+---*/
+
+delete Intl.DateTimeFormat.prototype[Symbol.toStringTag];
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object Object]");
+assert.sameValue(Object.prototype.toString.call(new Intl.DateTimeFormat()), "[object Object]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..f8e5d3db50
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toString.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype-@@tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.DateTimeFormat.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.DateTimeFormat.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.DateTimeFormat".
+features: [Symbol.toStringTag]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat.prototype), "[object Intl.DateTimeFormat]");
+assert.sameValue(Object.prototype.toString.call(new Intl.DateTimeFormat()), "[object Intl.DateTimeFormat]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..b118adeac8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype-@@tostringtag
+description: >
+ Property descriptor of Intl.DateTimeFormat.prototype[@@toStringTag].
+info: |
+ Intl.DateTimeFormat.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.DateTimeFormat".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.prototype, Symbol.toStringTag, {
+ value: "Intl.DateTimeFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/required-date-time-formats.js b/js/src/tests/test262/intl402/DateTimeFormat/required-date-time-formats.js
new file mode 100644
index 0000000000..6a17fc5edb
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/required-date-time-formats.js
@@ -0,0 +1,48 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.2.3_c
+description: >
+ Tests that Intl.DateTimeFormat provides the required date-time
+ format component subsets.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = ["de-DE", "en-US", "hi-IN", "id-ID", "ja-JP", "th-TH", "zh-Hans-CN", "zh-Hant-TW", "zxx"];
+var subsets = [
+ {weekday: "long", year: "numeric", month: "numeric", day: "numeric",
+ hour: "numeric", minute: "numeric", second: "numeric"},
+ {weekday: "long", year: "numeric", month: "numeric", day: "numeric"},
+ {year: "numeric", month: "numeric", day: "numeric"},
+ {year: "numeric", month: "numeric"},
+ {month: "numeric", day: "numeric"},
+ {hour: "numeric", minute: "numeric", second: "numeric"},
+ {hour: "numeric", minute: "numeric"}
+];
+
+locales.forEach(function (locale) {
+ subsets.forEach(function (subset) {
+ var format = new Intl.DateTimeFormat([locale], subset);
+ var actual = format.resolvedOptions();
+ getDateTimeComponents().forEach(function (component) {
+ if (actual.hasOwnProperty(component)) {
+ assert(subset.hasOwnProperty(component),
+ "Unrequested component " + component +
+ " added to requested subset " + JSON.stringify(subset) +
+ "; locale " + locale + ".");
+ assert.notSameValue(getDateTimeComponentValues(component).indexOf(actual[component]), -1,
+ "Invalid value " + actual[component] + " for date-time component " + component + "." +
+ " (Testing locale " + locale + "; subset " + JSON.stringify(subset) + ")");
+ } else {
+ assert.sameValue(subset.hasOwnProperty(component), false,
+ "Missing component " + component +
+ " from requested subset " + JSON.stringify(subset) +
+ "; locale " + locale + ".");
+ }
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/shell.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/subclassing.js b/js/src/tests/test262/intl402/DateTimeFormat/subclassing.js
new file mode 100644
index 0000000000..c7167676b8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/subclassing.js
@@ -0,0 +1,30 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.2
+description: Tests that Intl.DateTimeFormat can be subclassed.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// get a date-time format and have it format an array of dates for comparison with the subclass
+var locales = ["tlh", "id", "en"];
+var a = [new Date(0), Date.now(), new Date(Date.parse("1989-11-09T17:57:00Z"))];
+var referenceDateTimeFormat = new Intl.DateTimeFormat(locales);
+var referenceFormatted = a.map(referenceDateTimeFormat.format);
+
+class MyDateTimeFormat extends Intl.DateTimeFormat {
+ constructor(locales, options) {
+ super(locales, options);
+ // could initialize MyDateTimeFormat properties
+ }
+ // could add methods to MyDateTimeFormat.prototype
+}
+
+var format = new MyDateTimeFormat(locales);
+var actual = a.map(format.format);
+assert.compareArray(actual, referenceFormatted);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..8b4e2884e7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/basic.js
@@ -0,0 +1,25 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.2.2_a
+description: >
+ Tests that Intl.DateTimeFormat has a supportedLocalesOf property,
+ and it works as planned.
+author: Roozbeh Pournader
+---*/
+
+var defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
+var notSupported = 'zxx'; // "no linguistic content"
+var requestedLocales = [defaultLocale, notSupported];
+
+var supportedLocales;
+
+assert(Intl.DateTimeFormat.hasOwnProperty('supportedLocalesOf'), "Intl.DateTimeFormat doesn't have a supportedLocalesOf property.");
+
+supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/builtin.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/builtin.js
new file mode 100644
index 0000000000..5af3ea6504
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.2.2_L15
+description: >
+ Tests that Intl.DateTimeFormat.supportedLocalesOf meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.DateTimeFormat.supportedLocalesOf), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.DateTimeFormat.supportedLocalesOf),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.DateTimeFormat.supportedLocalesOf), Function.prototype);
+
+assert.sameValue(Intl.DateTimeFormat.supportedLocalesOf.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.DateTimeFormat.supportedLocalesOf), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..cf5e2db00b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.supportedlocalesof
+description: >
+ Intl.DateTimeFormat.supportedLocalesOf.length is 1.
+info: |
+ Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..ca0654027b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DateTimeFormat.supportedLocalesOf
+description: >
+ Intl.DateTimeFormat.supportedLocalesOf.name is "supportedLocalesOf".
+info: |
+ 12.3.2 Intl.DateTimeFormat.supportedLocalesOf (locales [ , options ])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..d5e93a5be4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.supportedlocalesof
+description: >
+ "supportedLocalesOf" property of Intl.DateTimeFormat.
+info: |
+ Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DateTimeFormat, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/taint-Object-prototype.js b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/taint-Object-prototype.js
new file mode 100644
index 0000000000..47ed85a18c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/supportedLocalesOf/taint-Object-prototype.js
@@ -0,0 +1,16 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 12.2.2_b
+description: >
+ Tests that Intl.DateTimeFormat.supportedLocalesOf doesn't access
+ arguments that it's not given.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Object.prototype, "1");
+new Intl.DateTimeFormat("und");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-date-time-components.js b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-date-time-components.js
new file mode 100644
index 0000000000..a05e77b80e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-date-time-components.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_22
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZone"]);
+
+var locale = new Intl.DateTimeFormat(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "DateTimeFormat returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-dayPeriod.js b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-dayPeriod.js
new file mode 100644
index 0000000000..6b51db065d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-dayPeriod.js
@@ -0,0 +1,18 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+includes: [testIntl.js]
+features: [Intl.DateTimeFormat-dayPeriod]
+---*/
+
+taintProperties(["dayPeriod"]);
+
+var locale = new Intl.DateTimeFormat(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "DateTimeFormat returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-fractionalSecondDigits.js b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-fractionalSecondDigits.js
new file mode 100644
index 0000000000..8792628e97
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype-fractionalSecondDigits.js
@@ -0,0 +1,18 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-createdatetimeformat
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+includes: [testIntl.js]
+features: [Intl.DateTimeFormat-fractionalSecondDigits]
+---*/
+
+taintProperties(["fractionalSecondDigits"]);
+
+var locale = new Intl.DateTimeFormat(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "DateTimeFormat returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype.js b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype.js
new file mode 100644
index 0000000000..5dd7b61535
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/taint-Object-prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_5
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["localeMatcher"]);
+
+var locale = new Intl.DateTimeFormat(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "DateTimeFormat returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/test-option-date-time-components.js b/js/src/tests/test262/intl402/DateTimeFormat/test-option-date-time-components.js
new file mode 100644
index 0000000000..a0e0a16dc7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/test-option-date-time-components.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_23
+description: >
+ Tests that the options for the date and time components are
+ processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+getDateTimeComponents().forEach(function (component) {
+ testOption(Intl.DateTimeFormat, component, "string", getDateTimeComponentValues(component), undefined, {isILD: true});
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/test-option-formatMatcher.js b/js/src/tests/test262/intl402/DateTimeFormat/test-option-formatMatcher.js
new file mode 100644
index 0000000000..41c25d6e99
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/test-option-formatMatcher.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_25
+description: Tests that the option formatMatcher is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.DateTimeFormat, "formatMatcher", "string", ["basic", "best fit"], "best fit", {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/test-option-hour12.js b/js/src/tests/test262/intl402/DateTimeFormat/test-option-hour12.js
new file mode 100644
index 0000000000..c6a4124bf9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/test-option-hour12.js
@@ -0,0 +1,16 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_18
+description: Tests that the option hour12 is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.DateTimeFormat, "hour12", "boolean", undefined, undefined,
+ {extra: {any: {hour: "numeric", minute: "numeric"}}});
+testOption(Intl.DateTimeFormat, "hour12", "boolean", undefined, undefined,
+ {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/test-option-localeMatcher.js b/js/src/tests/test262/intl402/DateTimeFormat/test-option-localeMatcher.js
new file mode 100644
index 0000000000..6adf184682
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/test-option-localeMatcher.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 12.1.1_6
+description: Tests that the option localeMatcher is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.DateTimeFormat, "localeMatcher", "string", ["lookup", "best fit"], "best fit", {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/this-value-ignored.js b/js/src/tests/test262/intl402/DateTimeFormat/this-value-ignored.js
new file mode 100644
index 0000000000..e96110d1ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/this-value-ignored.js
@@ -0,0 +1,37 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl-datetimeformat-constructor
+description: >
+ Tests that the this-value is ignored in DateTimeFormat, if the this-value
+ isn't a DateTimeFormat instance.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ if (Constructor === Intl.DateTimeFormat)
+ return;
+
+ var obj, newObj;
+
+ // variant 1: use constructor in a "new" expression
+ obj = new Constructor();
+ newObj = Intl.DateTimeFormat.call(obj);
+ assert.notSameValue(obj, newObj, "DateTimeFormat object created with \"new\" was not ignored as this-value.");
+
+ // variant 2: use constructor as a function
+ if (Constructor !== Intl.Collator &&
+ Constructor !== Intl.NumberFormat &&
+ Constructor !== Intl.DateTimeFormat)
+ {
+ // Newer Intl constructors are not callable as a function.
+ return;
+ }
+ obj = Constructor();
+ newObj = Intl.DateTimeFormat.call(obj);
+ assert.notSameValue(obj, newObj, "DateTimeFormat object created with constructor as function was not ignored as this-value.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/timezone-case-insensitive.js b/js/src/tests/test262/intl402/DateTimeFormat/timezone-case-insensitive.js
new file mode 100644
index 0000000000..f52468843a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/timezone-case-insensitive.js
@@ -0,0 +1,638 @@
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializedatetimeformat
+description: Time zone identifiers are case-normalized
+---*/
+
+const timeZoneIdentifiers = [
+ // IANA TZDB Zone names
+ "Africa/Abidjan",
+ "Africa/Algiers",
+ "Africa/Bissau",
+ "Africa/Cairo",
+ "Africa/Casablanca",
+ "Africa/Ceuta",
+ "Africa/El_Aaiun",
+ "Africa/Johannesburg",
+ "Africa/Juba",
+ "Africa/Khartoum",
+ "Africa/Lagos",
+ "Africa/Maputo",
+ "Africa/Monrovia",
+ "Africa/Nairobi",
+ "Africa/Ndjamena",
+ "Africa/Sao_Tome",
+ "Africa/Tripoli",
+ "Africa/Tunis",
+ "Africa/Windhoek",
+ "America/Adak",
+ "America/Anchorage",
+ "America/Araguaina",
+ "America/Argentina/Buenos_Aires",
+ "America/Argentina/Catamarca",
+ "America/Argentina/Cordoba",
+ "America/Argentina/Jujuy",
+ "America/Argentina/La_Rioja",
+ "America/Argentina/Mendoza",
+ "America/Argentina/Rio_Gallegos",
+ "America/Argentina/Salta",
+ "America/Argentina/San_Juan",
+ "America/Argentina/San_Luis",
+ "America/Argentina/Tucuman",
+ "America/Argentina/Ushuaia",
+ "America/Asuncion",
+ "America/Bahia",
+ "America/Bahia_Banderas",
+ "America/Barbados",
+ "America/Belem",
+ "America/Belize",
+ "America/Boa_Vista",
+ "America/Bogota",
+ "America/Boise",
+ "America/Cambridge_Bay",
+ "America/Campo_Grande",
+ "America/Cancun",
+ "America/Caracas",
+ "America/Cayenne",
+ "America/Chicago",
+ "America/Chihuahua",
+ // 'America/Ciudad_Juarez' // uncomment after Node supports this ID added in TZDB 2022g
+ "America/Costa_Rica",
+ "America/Cuiaba",
+ "America/Danmarkshavn",
+ "America/Dawson",
+ "America/Dawson_Creek",
+ "America/Denver",
+ "America/Detroit",
+ "America/Edmonton",
+ "America/Eirunepe",
+ "America/El_Salvador",
+ "America/Fort_Nelson",
+ "America/Fortaleza",
+ "America/Glace_Bay",
+ "America/Goose_Bay",
+ "America/Grand_Turk",
+ "America/Guatemala",
+ "America/Guayaquil",
+ "America/Guyana",
+ "America/Halifax",
+ "America/Havana",
+ "America/Hermosillo",
+ "America/Indiana/Indianapolis",
+ "America/Indiana/Knox",
+ "America/Indiana/Marengo",
+ "America/Indiana/Petersburg",
+ "America/Indiana/Tell_City",
+ "America/Indiana/Vevay",
+ "America/Indiana/Vincennes",
+ "America/Indiana/Winamac",
+ "America/Inuvik",
+ "America/Iqaluit",
+ "America/Jamaica",
+ "America/Juneau",
+ "America/Kentucky/Louisville",
+ "America/Kentucky/Monticello",
+ "America/La_Paz",
+ "America/Lima",
+ "America/Los_Angeles",
+ "America/Maceio",
+ "America/Managua",
+ "America/Manaus",
+ "America/Martinique",
+ "America/Matamoros",
+ "America/Mazatlan",
+ "America/Menominee",
+ "America/Merida",
+ "America/Metlakatla",
+ "America/Mexico_City",
+ "America/Miquelon",
+ "America/Moncton",
+ "America/Monterrey",
+ "America/Montevideo",
+ "America/New_York",
+ "America/Nome",
+ "America/Noronha",
+ "America/North_Dakota/Beulah",
+ "America/North_Dakota/Center",
+ "America/North_Dakota/New_Salem",
+ "America/Nuuk",
+ "America/Ojinaga",
+ "America/Panama",
+ "America/Paramaribo",
+ "America/Phoenix",
+ "America/Port-au-Prince",
+ "America/Porto_Velho",
+ "America/Puerto_Rico",
+ "America/Punta_Arenas",
+ "America/Rankin_Inlet",
+ "America/Recife",
+ "America/Regina",
+ "America/Resolute",
+ "America/Rio_Branco",
+ "America/Santarem",
+ "America/Santiago",
+ "America/Santo_Domingo",
+ "America/Sao_Paulo",
+ "America/Scoresbysund",
+ "America/Sitka",
+ "America/St_Johns",
+ "America/Swift_Current",
+ "America/Tegucigalpa",
+ "America/Thule",
+ "America/Tijuana",
+ "America/Toronto",
+ "America/Vancouver",
+ "America/Whitehorse",
+ "America/Winnipeg",
+ "America/Yakutat",
+ "America/Yellowknife",
+ "Antarctica/Casey",
+ "Antarctica/Davis",
+ "Antarctica/Macquarie",
+ "Antarctica/Mawson",
+ "Antarctica/Palmer",
+ "Antarctica/Rothera",
+ "Antarctica/Troll",
+ "Asia/Almaty",
+ "Asia/Amman",
+ "Asia/Anadyr",
+ "Asia/Aqtau",
+ "Asia/Aqtobe",
+ "Asia/Ashgabat",
+ "Asia/Atyrau",
+ "Asia/Baghdad",
+ "Asia/Baku",
+ "Asia/Bangkok",
+ "Asia/Barnaul",
+ "Asia/Beirut",
+ "Asia/Bishkek",
+ "Asia/Chita",
+ "Asia/Choibalsan",
+ "Asia/Colombo",
+ "Asia/Damascus",
+ "Asia/Dhaka",
+ "Asia/Dili",
+ "Asia/Dubai",
+ "Asia/Dushanbe",
+ "Asia/Famagusta",
+ "Asia/Gaza",
+ "Asia/Hebron",
+ "Asia/Ho_Chi_Minh",
+ "Asia/Hong_Kong",
+ "Asia/Hovd",
+ "Asia/Irkutsk",
+ "Asia/Jakarta",
+ "Asia/Jayapura",
+ "Asia/Jerusalem",
+ "Asia/Kabul",
+ "Asia/Kamchatka",
+ "Asia/Karachi",
+ "Asia/Kathmandu",
+ "Asia/Khandyga",
+ "Asia/Kolkata",
+ "Asia/Krasnoyarsk",
+ "Asia/Kuching",
+ "Asia/Macau",
+ "Asia/Magadan",
+ "Asia/Makassar",
+ "Asia/Manila",
+ "Asia/Nicosia",
+ "Asia/Novokuznetsk",
+ "Asia/Novosibirsk",
+ "Asia/Omsk",
+ "Asia/Oral",
+ "Asia/Pontianak",
+ "Asia/Pyongyang",
+ "Asia/Qatar",
+ "Asia/Qostanay",
+ "Asia/Qyzylorda",
+ "Asia/Riyadh",
+ "Asia/Sakhalin",
+ "Asia/Samarkand",
+ "Asia/Seoul",
+ "Asia/Shanghai",
+ "Asia/Singapore",
+ "Asia/Srednekolymsk",
+ "Asia/Taipei",
+ "Asia/Tashkent",
+ "Asia/Tbilisi",
+ "Asia/Tehran",
+ "Asia/Thimphu",
+ "Asia/Tokyo",
+ "Asia/Tomsk",
+ "Asia/Ulaanbaatar",
+ "Asia/Urumqi",
+ "Asia/Ust-Nera",
+ "Asia/Vladivostok",
+ "Asia/Yakutsk",
+ "Asia/Yangon",
+ "Asia/Yekaterinburg",
+ "Asia/Yerevan",
+ "Atlantic/Azores",
+ "Atlantic/Bermuda",
+ "Atlantic/Canary",
+ "Atlantic/Cape_Verde",
+ "Atlantic/Faroe",
+ "Atlantic/Madeira",
+ "Atlantic/South_Georgia",
+ "Atlantic/Stanley",
+ "Australia/Adelaide",
+ "Australia/Brisbane",
+ "Australia/Broken_Hill",
+ "Australia/Darwin",
+ "Australia/Eucla",
+ "Australia/Hobart",
+ "Australia/Lindeman",
+ "Australia/Lord_Howe",
+ "Australia/Melbourne",
+ "Australia/Perth",
+ "Australia/Sydney",
+ "CET",
+ "CST6CDT",
+ "EET",
+ "EST",
+ "EST5EDT",
+ "Etc/GMT",
+ "Etc/GMT+1",
+ "Etc/GMT+10",
+ "Etc/GMT+11",
+ "Etc/GMT+12",
+ "Etc/GMT+2",
+ "Etc/GMT+3",
+ "Etc/GMT+4",
+ "Etc/GMT+5",
+ "Etc/GMT+6",
+ "Etc/GMT+7",
+ "Etc/GMT+8",
+ "Etc/GMT+9",
+ "Etc/GMT-1",
+ "Etc/GMT-10",
+ "Etc/GMT-11",
+ "Etc/GMT-12",
+ "Etc/GMT-13",
+ "Etc/GMT-14",
+ "Etc/GMT-2",
+ "Etc/GMT-3",
+ "Etc/GMT-4",
+ "Etc/GMT-5",
+ "Etc/GMT-6",
+ "Etc/GMT-7",
+ "Etc/GMT-8",
+ "Etc/GMT-9",
+ "Etc/UTC",
+ "Europe/Andorra",
+ "Europe/Astrakhan",
+ "Europe/Athens",
+ "Europe/Belgrade",
+ "Europe/Berlin",
+ "Europe/Brussels",
+ "Europe/Bucharest",
+ "Europe/Budapest",
+ "Europe/Chisinau",
+ "Europe/Dublin",
+ "Europe/Gibraltar",
+ "Europe/Helsinki",
+ "Europe/Istanbul",
+ "Europe/Kaliningrad",
+ "Europe/Kirov",
+ "Europe/Kyiv",
+ "Europe/Lisbon",
+ "Europe/London",
+ "Europe/Madrid",
+ "Europe/Malta",
+ "Europe/Minsk",
+ "Europe/Moscow",
+ "Europe/Paris",
+ "Europe/Prague",
+ "Europe/Riga",
+ "Europe/Rome",
+ "Europe/Samara",
+ "Europe/Saratov",
+ "Europe/Simferopol",
+ "Europe/Sofia",
+ "Europe/Tallinn",
+ "Europe/Tirane",
+ "Europe/Ulyanovsk",
+ "Europe/Vienna",
+ "Europe/Vilnius",
+ "Europe/Volgograd",
+ "Europe/Warsaw",
+ "Europe/Zurich",
+ "HST",
+ "Indian/Chagos",
+ "Indian/Maldives",
+ "Indian/Mauritius",
+ "MET",
+ "MST",
+ "MST7MDT",
+ "PST8PDT",
+ "Pacific/Apia",
+ "Pacific/Auckland",
+ "Pacific/Bougainville",
+ "Pacific/Chatham",
+ "Pacific/Easter",
+ "Pacific/Efate",
+ "Pacific/Fakaofo",
+ "Pacific/Fiji",
+ "Pacific/Galapagos",
+ "Pacific/Gambier",
+ "Pacific/Guadalcanal",
+ "Pacific/Guam",
+ "Pacific/Honolulu",
+ "Pacific/Kanton",
+ "Pacific/Kiritimati",
+ "Pacific/Kosrae",
+ "Pacific/Kwajalein",
+ "Pacific/Marquesas",
+ "Pacific/Nauru",
+ "Pacific/Niue",
+ "Pacific/Norfolk",
+ "Pacific/Noumea",
+ "Pacific/Pago_Pago",
+ "Pacific/Palau",
+ "Pacific/Pitcairn",
+ "Pacific/Port_Moresby",
+ "Pacific/Rarotonga",
+ "Pacific/Tahiti",
+ "Pacific/Tarawa",
+ "Pacific/Tongatapu",
+
+ // IANA TZDB Link names
+ "WET",
+ "Africa/Accra",
+ "Africa/Addis_Ababa",
+ "Africa/Asmara",
+ "Africa/Asmera",
+ "Africa/Bamako",
+ "Africa/Bangui",
+ "Africa/Banjul",
+ "Africa/Blantyre",
+ "Africa/Brazzaville",
+ "Africa/Bujumbura",
+ "Africa/Conakry",
+ "Africa/Dakar",
+ "Africa/Dar_es_Salaam",
+ "Africa/Djibouti",
+ "Africa/Douala",
+ "Africa/Freetown",
+ "Africa/Gaborone",
+ "Africa/Harare",
+ "Africa/Kampala",
+ "Africa/Kigali",
+ "Africa/Kinshasa",
+ "Africa/Libreville",
+ "Africa/Lome",
+ "Africa/Luanda",
+ "Africa/Lubumbashi",
+ "Africa/Lusaka",
+ "Africa/Malabo",
+ "Africa/Maseru",
+ "Africa/Mbabane",
+ "Africa/Mogadishu",
+ "Africa/Niamey",
+ "Africa/Nouakchott",
+ "Africa/Ouagadougou",
+ "Africa/Porto-Novo",
+ "Africa/Timbuktu",
+ "America/Anguilla",
+ "America/Antigua",
+ "America/Argentina/ComodRivadavia",
+ "America/Aruba",
+ "America/Atikokan",
+ "America/Atka",
+ "America/Blanc-Sablon",
+ "America/Buenos_Aires",
+ "America/Catamarca",
+ "America/Cayman",
+ "America/Coral_Harbour",
+ "America/Cordoba",
+ "America/Creston",
+ "America/Curacao",
+ "America/Dominica",
+ "America/Ensenada",
+ "America/Fort_Wayne",
+ "America/Godthab",
+ "America/Grenada",
+ "America/Guadeloupe",
+ "America/Indianapolis",
+ "America/Jujuy",
+ "America/Knox_IN",
+ "America/Kralendijk",
+ "America/Louisville",
+ "America/Lower_Princes",
+ "America/Marigot",
+ "America/Mendoza",
+ "America/Montreal",
+ "America/Montserrat",
+ "America/Nassau",
+ "America/Nipigon",
+ "America/Pangnirtung",
+ "America/Port_of_Spain",
+ "America/Porto_Acre",
+ "America/Rainy_River",
+ "America/Rosario",
+ "America/Santa_Isabel",
+ "America/Shiprock",
+ "America/St_Barthelemy",
+ "America/St_Kitts",
+ "America/St_Lucia",
+ "America/St_Thomas",
+ "America/St_Vincent",
+ "America/Thunder_Bay",
+ "America/Tortola",
+ "America/Virgin",
+ "Antarctica/DumontDUrville",
+ "Antarctica/McMurdo",
+ "Antarctica/South_Pole",
+ "Antarctica/Syowa",
+ "Antarctica/Vostok",
+ "Arctic/Longyearbyen",
+ "Asia/Aden",
+ "Asia/Ashkhabad",
+ "Asia/Bahrain",
+ "Asia/Brunei",
+ "Asia/Calcutta",
+ "Asia/Chongqing",
+ "Asia/Chungking",
+ "Asia/Dacca",
+ "Asia/Harbin",
+ "Asia/Istanbul",
+ "Asia/Kashgar",
+ "Asia/Katmandu",
+ "Asia/Kuala_Lumpur",
+ "Asia/Kuwait",
+ "Asia/Macao",
+ "Asia/Muscat",
+ "Asia/Phnom_Penh",
+ "Asia/Rangoon",
+ "Asia/Saigon",
+ "Asia/Tel_Aviv",
+ "Asia/Thimbu",
+ "Asia/Ujung_Pandang",
+ "Asia/Ulan_Bator",
+ "Asia/Vientiane",
+ "Atlantic/Faeroe",
+ "Atlantic/Jan_Mayen",
+ "Atlantic/Reykjavik",
+ "Atlantic/St_Helena",
+ "Australia/ACT",
+ "Australia/Canberra",
+ "Australia/Currie",
+ "Australia/LHI",
+ "Australia/NSW",
+ "Australia/North",
+ "Australia/Queensland",
+ "Australia/South",
+ "Australia/Tasmania",
+ "Australia/Victoria",
+ "Australia/West",
+ "Australia/Yancowinna",
+ "Brazil/Acre",
+ "Brazil/DeNoronha",
+ "Brazil/East",
+ "Brazil/West",
+ "Canada/Atlantic",
+ "Canada/Central",
+ "Canada/Eastern",
+ "Canada/Mountain",
+ "Canada/Newfoundland",
+ "Canada/Pacific",
+ "Canada/Saskatchewan",
+ "Canada/Yukon",
+ "Chile/Continental",
+ "Chile/EasterIsland",
+ "Cuba",
+ "Egypt",
+ "Eire",
+ "Etc/GMT+0",
+ "Etc/GMT-0",
+ "Etc/GMT0",
+ "Etc/Greenwich",
+ "Etc/UCT",
+ "Etc/Universal",
+ "Etc/Zulu",
+ "Europe/Amsterdam",
+ "Europe/Belfast",
+ "Europe/Bratislava",
+ "Europe/Busingen",
+ "Europe/Copenhagen",
+ "Europe/Guernsey",
+ "Europe/Isle_of_Man",
+ "Europe/Jersey",
+ "Europe/Kiev",
+ "Europe/Ljubljana",
+ "Europe/Luxembourg",
+ "Europe/Mariehamn",
+ "Europe/Monaco",
+ "Europe/Nicosia",
+ "Europe/Oslo",
+ "Europe/Podgorica",
+ "Europe/San_Marino",
+ "Europe/Sarajevo",
+ "Europe/Skopje",
+ "Europe/Stockholm",
+ "Europe/Tiraspol",
+ "Europe/Uzhgorod",
+ "Europe/Vaduz",
+ "Europe/Vatican",
+ "Europe/Zagreb",
+ "Europe/Zaporozhye",
+ "GB",
+ "GB-Eire",
+ "GMT",
+ "GMT+0",
+ "GMT-0",
+ "GMT0",
+ "Greenwich",
+ "Hongkong",
+ "Iceland",
+ "Indian/Antananarivo",
+ "Indian/Christmas",
+ "Indian/Cocos",
+ "Indian/Comoro",
+ "Indian/Kerguelen",
+ "Indian/Mahe",
+ "Indian/Mayotte",
+ "Indian/Reunion",
+ "Iran",
+ "Israel",
+ "Jamaica",
+ "Japan",
+ "Kwajalein",
+ "Libya",
+ "Mexico/BajaNorte",
+ "Mexico/BajaSur",
+ "Mexico/General",
+ "NZ",
+ "NZ-CHAT",
+ "Navajo",
+ "PRC",
+ "Pacific/Chuuk",
+ "Pacific/Enderbury",
+ "Pacific/Funafuti",
+ "Pacific/Johnston",
+ "Pacific/Majuro",
+ "Pacific/Midway",
+ "Pacific/Pohnpei",
+ "Pacific/Ponape",
+ "Pacific/Saipan",
+ "Pacific/Samoa",
+ "Pacific/Truk",
+ "Pacific/Wake",
+ "Pacific/Wallis",
+ "Pacific/Yap",
+ "Poland",
+ "Portugal",
+ "ROC",
+ "ROK",
+ "Singapore",
+ "Turkey",
+ "UCT",
+ "US/Alaska",
+ "US/Aleutian",
+ "US/Arizona",
+ "US/Central",
+ "US/East-Indiana",
+ "US/Eastern",
+ "US/Hawaii",
+ "US/Indiana-Starke",
+ "US/Michigan",
+ "US/Mountain",
+ "US/Pacific",
+ "US/Pacific-New",
+ "US/Samoa",
+ "UTC",
+ "Universal",
+ "W-SU",
+ "Zulu"
+];
+
+// We want to test all available named time zone identifiers (both primary and non-primary),
+// but no ECMAScript built-in API exposes that list. So we use a union of two sources:
+// 1. A hard-coded list of Zone and Link identifiers from the 2022g version of IANA TZDB.
+// 2. Canonical IDs exposed by Intl.supportedValuesOf('timeZone'), which ensures that IDs
+// added to TZDB later than 2022g will be tested. (New IDs are almost always added as primary.)
+const ids = [...new Set([...timeZoneIdentifiers, ...Intl.supportedValuesOf("timeZone")])];
+for (const id of ids) {
+ const lower = id.toLowerCase();
+ const upper = id.toUpperCase();
+ assert.sameValue(
+ new Intl.DateTimeFormat("en", { timeZone: id }).resolvedOptions().timeZone,
+ id,
+ `Time zone created from string "${id}"`
+ );
+ assert.sameValue(
+ new Intl.DateTimeFormat("en", { timeZone: upper }).resolvedOptions().timeZone,
+ id,
+ `Time zone created from string "${upper}"`
+ );
+ assert.sameValue(
+ new Intl.DateTimeFormat("en", { timeZone: lower }).resolvedOptions().timeZone,
+ id,
+ `Time zone created from string "${lower}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/timezone-invalid.js b/js/src/tests/test262/intl402/DateTimeFormat/timezone-invalid.js
new file mode 100644
index 0000000000..f42ffd0201
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/timezone-invalid.js
@@ -0,0 +1,28 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.4_b
+description: Tests that invalid time zone names are not accepted.
+author: Norbert Lindenberg
+---*/
+
+var invalidTimeZoneNames = [
+ "",
+ "MEZ", // localized abbreviation
+ "Pacific Time", // localized long form
+ "cnsha", // BCP 47 time zone code
+ "invalid", // as the name says
+ "Europe/İstanbul", // non-ASCII letter
+ "asıa/baku", // non-ASCII letter
+ "europe/brußels" // non-ASCII letter
+];
+
+invalidTimeZoneNames.forEach(function (name) {
+ // this must throw an exception for an invalid time zone name
+ assert.throws(RangeError, function() {
+ var format = new Intl.DateTimeFormat(["de-de"], {timeZone: name});
+ }, "Invalid time zone name " + name + " was not rejected.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/timezone-not-canonicalized.js b/js/src/tests/test262/intl402/DateTimeFormat/timezone-not-canonicalized.js
new file mode 100644
index 0000000000..7218ad448b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/timezone-not-canonicalized.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializedatetimeformat
+description: Time zone identifiers are not canonicalized before storing in internal slots
+---*/
+
+const baseOptions = {
+ timeZoneName: "long",
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric"
+};
+const dtf1 = new Intl.DateTimeFormat("en", { ...baseOptions, timeZone: "Asia/Calcutta" });
+const dtf2 = new Intl.DateTimeFormat("en", { ...baseOptions, timeZone: "Asia/Kolkata" });
+
+const resolvedId1 = dtf1.resolvedOptions().timeZone;
+const resolvedId2 = dtf2.resolvedOptions().timeZone;
+
+const output1 = dtf1.format(0);
+const output2 = dtf2.format(0);
+
+assert.sameValue(output1, output2);
+assert.sameValue(resolvedId1, "Asia/Calcutta");
+assert.sameValue(resolvedId2, "Asia/Kolkata");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DateTimeFormat/timezone-utc.js b/js/src/tests/test262/intl402/DateTimeFormat/timezone-utc.js
new file mode 100644
index 0000000000..f27d109b1a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DateTimeFormat/timezone-utc.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.4_a
+description: Tests that valid time zone names are accepted.
+author: Norbert Lindenberg
+---*/
+
+var validTimeZoneNames = [
+ "UTC",
+ "utc" // time zone names are case-insensitive
+];
+
+validTimeZoneNames.forEach(function (name) {
+ // this must not throw an exception for a valid time zone name
+ var format = new Intl.DateTimeFormat(["de-de"], {timeZone: name});
+ assert.sameValue(format.resolvedOptions().timeZone, name.toUpperCase(), "Time zone name " + name + " was not correctly accepted.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/browser.js b/js/src/tests/test262/intl402/DisplayNames/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/browser.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/ctor-custom-get-prototype-poison-throws.js b/js/src/tests/test262/intl402/DisplayNames/ctor-custom-get-prototype-poison-throws.js
new file mode 100644
index 0000000000..cb68a161ba
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/ctor-custom-get-prototype-poison-throws.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt from Get Prototype from a custom NewTarget
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ ...
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ 3. Let proto be ? Get(constructor, "prototype").
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.DisplayNames, Reflect, Proxy]
+---*/
+
+var custom = new Proxy(new Function(), {
+ get(target, key) {
+ if (key === 'prototype') {
+ throw new Test262Error();
+ }
+
+ return target[key];
+ }
+});
+
+assert.throws(Test262Error, () => {
+ Reflect.construct(Intl.DisplayNames, [], custom);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/ctor-custom-prototype.js b/js/src/tests/test262/intl402/DisplayNames/ctor-custom-prototype.js
new file mode 100644
index 0000000000..7ee1e0d702
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/ctor-custom-prototype.js
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Custom Prototype of the returned object based on the NewTarget
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ ...
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ 3. Let proto be ? Get(constructor, "prototype").
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.DisplayNames, Reflect]
+---*/
+
+var custom = new Function();
+custom.prototype = {};
+
+const obj = Reflect.construct(Intl.DisplayNames, [undefined, {type: 'language'}], custom);
+
+assert.sameValue(Object.getPrototypeOf(obj), custom.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/ctor-default-prototype.js b/js/src/tests/test262/intl402/DisplayNames/ctor-default-prototype.js
new file mode 100644
index 0000000000..cce416f9ff
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/ctor-default-prototype.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Prototype of the returned object is DisplayNames.prototype
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 27. Return displayNames.
+features: [Intl.DisplayNames]
+---*/
+
+var obj = new Intl.DisplayNames(undefined, {type: 'language'});
+
+assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/instance-extensible.js b/js/src/tests/test262/intl402/DisplayNames/instance-extensible.js
new file mode 100644
index 0000000000..93d002091f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/instance-extensible.js
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Instance is extensible
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ ObjectCreate ( proto [ , internalSlotsList ] )
+
+ ...
+ 2. Let obj be a newly created object with an internal slot for each name in internalSlotsList.
+ 3. Set obj's essential internal methods to the default ordinary object definitions specified in 9.1.
+ 4. Set obj.[[Prototype]] to proto.
+ 5. Set obj.[[Extensible]] to true.
+ 6. Return obj.
+features: [Intl.DisplayNames]
+---*/
+
+var obj = new Intl.DisplayNames(undefined, {type: 'language'});
+
+assert(Object.isExtensible(obj));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/length.js b/js/src/tests/test262/intl402/DisplayNames/length.js
new file mode 100644
index 0000000000..2e8c435bd9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/length.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Intl.DisplayNames.length is 2.
+info: |
+ ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+verifyProperty(Intl.DisplayNames, "length", {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/locales-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/locales-invalid-throws.js
new file mode 100644
index 0000000000..70bc899905
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/locales-invalid-throws.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Throws TypeError if locales is not undefined, a string, or an array-like object.
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
+ ...
+
+ CanonicalizeLocaleList ( locales )
+
+ 1. If locales is undefined, then
+ a. Return a new empty List.
+ 2. Let seen be a new empty List.
+ 3. If Type(locales) is String, then
+ a. Let O be CreateArrayFromList(« locales »).
+ 4. Else,
+ a. Let O be ? ToObject(locales).
+ 5. Let len be ? ToLength(? Get(O, "length")).
+features: [Intl.DisplayNames]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames(null);
+}, 'null');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/locales-length-poison-throws.js b/js/src/tests/test262/intl402/DisplayNames/locales-length-poison-throws.js
new file mode 100644
index 0000000000..5469678ecc
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/locales-length-poison-throws.js
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from Get Locales length
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
+ ...
+
+ CanonicalizeLocaleList ( locales )
+
+ 1. If locales is undefined, then
+ a. Return a new empty List.
+ 2. Let seen be a new empty List.
+ 3. If Type(locales) is String, then
+ a. Let O be CreateArrayFromList(« locales »).
+ 4. Else,
+ a. Let O be ? ToObject(locales).
+ 5. Let len be ? ToLength(? Get(O, "length")).
+features: [Intl.DisplayNames]
+---*/
+
+var locales = {};
+
+Object.defineProperty(locales, 'length', {
+ get() {
+ throw new Test262Error();
+ }
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames(locales);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/locales-length-tolength-throws.js b/js/src/tests/test262/intl402/DisplayNames/locales-length-tolength-throws.js
new file mode 100644
index 0000000000..a9c8b3e1e0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/locales-length-tolength-throws.js
@@ -0,0 +1,75 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from Locales invalid length
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
+ ...
+
+ CanonicalizeLocaleList ( locales )
+
+ 1. If locales is undefined, then
+ a. Return a new empty List.
+ 2. Let seen be a new empty List.
+ 3. If Type(locales) is String, then
+ a. Let O be CreateArrayFromList(« locales »).
+ 4. Else,
+ a. Let O be ? ToObject(locales).
+ 5. Let len be ? ToLength(? Get(O, "length")).
+
+ ToLength ( argument )
+
+ 1. Let len be ? ToInteger(argument).
+ ...
+features: [Intl.DisplayNames, Symbol, BigInt]
+---*/
+
+var locales = {
+ length: {
+ valueOf() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames(locales);
+}, 'poisoned valueOf for ToNumber');
+
+locales.length = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames(locales);
+}, 'poisoned ToPrimitive for ToNumber');
+
+locales.length = {
+ toString() {
+ throw new Test262Error();
+ }
+};
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames(locales);
+}, 'poisoned toString for ToNumber');
+
+locales.length = Symbol();
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames(locales);
+}, 'length is Symbol');
+
+locales.length = BigInt(1);
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames(locales);
+}, 'length is BigInt');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/locales-symbol-length.js b/js/src/tests/test262/intl402/DisplayNames/locales-symbol-length.js
new file mode 100644
index 0000000000..53f07eca22
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/locales-symbol-length.js
@@ -0,0 +1,64 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ CanonicalizeLocaleList tries to fetch length from Object.
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+
+ CanonicalizeLocaleList ( locales )
+
+ 1. If locales is undefined, then
+ a. Return a new empty List.
+ 2. Let seen be a new empty List.
+ 3. If Type(locales) is String, then
+ a. Let O be CreateArrayFromList(« locales »).
+ 4. Else,
+ a. Let O be ? ToObject(locales).
+ 5. Let len be ? ToLength(? Get(O, "length")).
+features: [Intl.DisplayNames, Symbol]
+locale: [en]
+includes: [compareArray.js]
+---*/
+
+let calls = [];
+let symbol = Symbol();
+
+Symbol.prototype.length = 1;
+
+Object.defineProperty(Symbol.prototype, 'length', {
+ get() {
+ assert.notSameValue(this, symbol, 'this is an object from given symbol');
+ assert.sameValue(this.valueOf(), symbol, 'internal value is the symbol');
+ assert(this instanceof Symbol);
+ calls.push('length');
+ return 1;
+ }
+});
+
+Object.defineProperty(Symbol.prototype, '0', {
+ get() {
+ assert.notSameValue(this, symbol, 'this is an object from given symbol');
+ assert.sameValue(this.valueOf(), symbol, 'internal value is the symbol');
+ assert(this instanceof Symbol);
+ calls.push('0');
+ return 'en';
+ }
+});
+
+new Intl.DisplayNames(symbol, {type: 'language'});
+
+assert.compareArray(calls, ['length', '0']);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/name.js b/js/src/tests/test262/intl402/DisplayNames/name.js
new file mode 100644
index 0000000000..6cfaf9855f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Intl.DisplayNames.name is "DisplayNames".
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value is a
+ String.
+
+ Unless otherwise specified, the name property of a built-in Function object,
+ if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]:
+ false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+verifyProperty(Intl.DisplayNames, "name", {
+ value: "DisplayNames",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-fallback-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-fallback-abrupt-throws.js
new file mode 100644
index 0000000000..6a798c142f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-fallback-abrupt-throws.js
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption fallback
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = { type: 'language' };
+Object.defineProperty(options, 'fallback', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-fallback-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-fallback-invalid-throws.js
new file mode 100644
index 0000000000..c1d6c5ae21
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-fallback-invalid-throws.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from an invalid fallback option
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », "language").
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ ...
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {fallback: 'err', type: 'language'});
+}, 'err');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {fallback: 'non', type: 'language'});
+}, 'non, not none');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {fallback: null, type: 'language'});
+}, 'null');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {fallback: '', type: 'language'});
+}, 'the empty string');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {fallback: ['code', 'none'], type: 'language'});
+}, 'an array with the valid options is not necessarily valid');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-fallback-toString-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-fallback-toString-abrupt-throws.js
new file mode 100644
index 0000000000..231117aee4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-fallback-toString-abrupt-throws.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption fallback
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames, Symbol]
+locale: [en]
+---*/
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language', fallback: { toString() { throw new Test262Error(); }}
+ });
+}, 'from toString');
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ fallback: {toString: undefined, valueOf() {throw new Test262Error(); }}
+ });
+}, 'from valueOf');
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ fallback: { [Symbol.toPrimitive]() { throw new Test262Error(); } }
+ });
+}, 'from ToPrimitive');
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ fallback: Symbol()
+ });
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-fallback-valid.js b/js/src/tests/test262/intl402/DisplayNames/options-fallback-valid.js
new file mode 100644
index 0000000000..b97245a3d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-fallback-valid.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Valid options for fallback
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const fallbacks = [
+ undefined,
+ 'code',
+ 'none'
+];
+
+fallbacks.forEach(fallback => {
+ const obj = new Intl.DisplayNames('en', { fallback, type: 'language'});
+
+ assert(obj instanceof Intl.DisplayNames, `instanceof check - ${fallback}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype, `proto check - ${fallback}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-getoptionsobject.js b/js/src/tests/test262/intl402/DisplayNames/options-getoptionsobject.js
new file mode 100644
index 0000000000..a8046db499
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-getoptionsobject.js
@@ -0,0 +1,26 @@
+// Copyright 2021 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: Checks handling of non-object option arguments to the DisplayNames constructor.
+info: |
+ Intl.DisplayNames ( locales, options )
+features: [Intl.DisplayNames,BigInt]
+---*/
+
+const optionsArguments = [
+ null,
+ true,
+ false,
+ "test",
+ 7,
+ Symbol(),
+ 123456789n,
+];
+
+for (const options of optionsArguments) {
+ assert.throws(TypeError, function() { new Intl.DisplayNames([], options) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-abrupt-throws.js
new file mode 100644
index 0000000000..4c4604fd9e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-abrupt-throws.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: Return abrupt completion from GetOption fallback
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 24. Let languageDisplay be ? GetOption(options, "languageDisplay", "string", « "dialect", "standard" », "dialect").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames-v2]
+locale: [en]
+---*/
+
+var options = { type: 'language' };
+Object.defineProperty(options, 'languageDisplay', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-invalid-throws.js
new file mode 100644
index 0000000000..95a6f3a032
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-invalid-throws.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from an invalid fallback option
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », "language").
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 24. Let languageDisplay be ? GetOption(options, "languageDisplay", "string", « "dialect", "standard" », "dialect").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ ...
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.DisplayNames-v2]
+locale: [en]
+---*/
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {languageDisplay: 'err', type: 'language'});
+}, 'err');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {languageDisplay: 'standar', type: 'language'});
+}, 'standar, not standard');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {languageDisplay: null, type: 'language'});
+}, 'null');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {languageDisplay: '', type: 'language'});
+}, 'the empty string');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {languageDisplay: ['dialect', 'standard'], type: 'language'});
+}, 'an array with the valid options is not necessarily valid');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-toString-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-toString-abrupt-throws.js
new file mode 100644
index 0000000000..96a4e3ce7c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-toString-abrupt-throws.js
@@ -0,0 +1,57 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption fallback
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 24. Let languageDisplay be ? GetOption(options, "languageDisplay", "string", « "dialect", "standard" », "dialect").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames-v2, Symbol]
+locale: [en]
+---*/
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language', languageDisplay: { toString() { throw new Test262Error(); }}
+ });
+}, 'from toString');
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ languageDisplay: {toString: undefined, valueOf() {throw new Test262Error(); }}
+ });
+}, 'from valueOf');
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ languageDisplay: { [Symbol.toPrimitive]() { throw new Test262Error(); } }
+ });
+}, 'from ToPrimitive');
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', {
+ type: 'language',
+ languageDisplay: Symbol()
+ });
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-valid.js b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-valid.js
new file mode 100644
index 0000000000..3b6ce728d4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-languagedisplay-valid.js
@@ -0,0 +1,49 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: Valid options for languageDisplay
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 24. Let languageDisplay be ? GetOption(options, "languageDisplay", "string", « "dialect", "standard" », "dialect").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames-v2]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const languageDisplays = [
+ undefined,
+ 'dialect',
+ 'standard'
+];
+
+languageDisplays.forEach(languageDisplay => {
+ const obj = new Intl.DisplayNames('en', { languageDisplay, type: 'language'});
+
+ assert(obj instanceof Intl.DisplayNames, `instanceof check - ${languageDisplay}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype, `proto check - ${languageDisplay}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-abrupt-throws.js
new file mode 100644
index 0000000000..1cb0a8013c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-abrupt-throws.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption localeMatcher
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = {};
+Object.defineProperty(options, 'localeMatcher', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-invalid-throws.js
new file mode 100644
index 0000000000..a0f1cfd744
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-invalid-throws.js
@@ -0,0 +1,69 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from an invalid localeMatcher option
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ ...
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = {
+ localeMatcher: 'bestfit' // not "best fit"
+};
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'bestfit');
+
+options.localeMatcher = 'look up';
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'look up');
+
+options.localeMatcher = null;
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'null');
+
+options.localeMatcher = '';
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'the empty string');
+
+// The world could burn
+options.localeMatcher = ['lookup', 'best fit'];
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'an array with the valid options is not necessarily valid');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-toString-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-toString-abrupt-throws.js
new file mode 100644
index 0000000000..64ecd3c1e9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-toString-abrupt-throws.js
@@ -0,0 +1,69 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption localeMatcher
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames, Symbol]
+locale: [en]
+---*/
+
+var options = {
+ localeMatcher: {
+ toString() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from toString');
+
+options.localeMatcher = {
+ toString: undefined,
+ valueOf() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from valueOf');
+
+options.localeMatcher = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from ToPrimitive');
+
+options.localeMatcher = Symbol();
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-valid.js b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-valid.js
new file mode 100644
index 0000000000..c934443d8a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-localeMatcher-valid.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Valid options for localeMatcher
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const localeMatchers = [
+ undefined,
+ 'lookup',
+ 'best fit'
+];
+
+localeMatchers.forEach(localeMatcher => {
+ const obj = new Intl.DisplayNames('en', { localeMatcher, type: 'language' });
+
+ assert(obj instanceof Intl.DisplayNames, `instanceof check - ${localeMatcher}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype, `proto check - ${localeMatcher}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-null-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-null-throws.js
new file mode 100644
index 0000000000..dca7bf9caa
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-null-throws.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Throws TypeError if options is null
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', null);
+}, 'null');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-random-properties-unchecked.js b/js/src/tests/test262/intl402/DisplayNames/options-random-properties-unchecked.js
new file mode 100644
index 0000000000..2bd057a4dd
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-random-properties-unchecked.js
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Random options are not checked or used, including case sensitive
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = { type: 'language' };
+Object.defineProperty(options, 'fallBack', {
+ get() { throw new Test262Error(); }
+});
+Object.defineProperty(options, 'localematcher', {
+ get() { throw new Test262Error(); }
+});
+Object.defineProperty(options, 'locale-matcher', {
+ get() { throw new Test262Error(); }
+});
+Object.defineProperty(options, 'Type', {
+ get() { throw new Test262Error(); }
+});
+
+var obj = new Intl.DisplayNames('en', options);
+
+assert(obj instanceof Intl.DisplayNames);
+assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-style-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-style-abrupt-throws.js
new file mode 100644
index 0000000000..51d7d4a6d8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-style-abrupt-throws.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption style
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 13. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency", "weekday", "month", "quarter", "dayPeriod", "dateTimeField" », "language").
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = {};
+Object.defineProperty(options, 'style', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-style-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-style-invalid-throws.js
new file mode 100644
index 0000000000..76083d3793
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-style-invalid-throws.js
@@ -0,0 +1,77 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from an invalid style option
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ ...
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = {
+ style: 'small'
+};
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'small');
+
+options.style = 'very long';
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'very long');
+
+options.style = 'full';
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'full');
+
+options.style = null;
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'null');
+
+options.style = '';
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'the empty string');
+
+options.style = ['narrow', 'short', 'long'];
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'an array with the valid options is not necessarily valid');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-style-toString-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-style-toString-abrupt-throws.js
new file mode 100644
index 0000000000..ac03b3ce1e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-style-toString-abrupt-throws.js
@@ -0,0 +1,72 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption style
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames, Symbol]
+locale: [en]
+---*/
+
+var options = {
+ style: {
+ toString() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from toString');
+
+options.style = {
+ toString: undefined,
+ valueOf() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from valueOf');
+
+options.style = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from ToPrimitive');
+
+options.style = Symbol();
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-style-valid.js b/js/src/tests/test262/intl402/DisplayNames/options-style-valid.js
new file mode 100644
index 0000000000..e2a8fd9890
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-style-valid.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Valid options for localeMatcher
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const styles = [
+ undefined,
+ 'narrow',
+ 'short',
+ 'long'
+];
+
+const types = ['language', 'region', 'script', 'currency'];
+
+types.forEach( type => {
+ styles.forEach(style => {
+ var obj = new Intl.DisplayNames('en', { style, type });
+
+ assert(obj instanceof Intl.DisplayNames, `instanceof check - ${style}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype, `proto check - ${style}`);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-type-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-type-abrupt-throws.js
new file mode 100644
index 0000000000..d7362cd778
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-type-abrupt-throws.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption type
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 13. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency", "weekday", "month", "quarter", "dayPeriod", "dateTimeField" », "language").
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+var options = {};
+Object.defineProperty(options, 'type', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-type-invalid-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-type-invalid-throws.js
new file mode 100644
index 0000000000..bdbaecae82
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-type-invalid-throws.js
@@ -0,0 +1,78 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from an invalid type option
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ ...
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', undefined);
+}, 'undefined options');
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', {});
+}, '{} options');
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', {type: undefined});
+}, 'undefined type');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: 'lang'});
+}, 'type = lang');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: 'dayPeriod'});
+}, 'dayPeriod not supported yet');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: 'weekday'});
+}, 'weekday not supported yet');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: null});
+}, 'type = null');
+
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: ''});
+}, 'type = ""');
+
+assert.throws(RangeError, () => {
+ new Intl.DisplayNames('en', {type: ['language', 'region', 'script', 'currency']});
+}, 'an array with the valid options is not necessarily valid');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-type-toString-abrupt-throws.js b/js/src/tests/test262/intl402/DisplayNames/options-type-toString-abrupt-throws.js
new file mode 100644
index 0000000000..36f43563cf
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-type-toString-abrupt-throws.js
@@ -0,0 +1,76 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Return abrupt completion from GetOption type
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 13. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency", "weekday", "month", "quarter", "dayPeriod", "dateTimeField" », "language").
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames, Symbol]
+locale: [en]
+---*/
+
+var options = {
+ type: {
+ toString() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from toString');
+
+options.type = {
+ toString: undefined,
+ valueOf() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from valueOf');
+
+options.type = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.DisplayNames('en', options);
+}, 'from ToPrimitive');
+
+options.type = Symbol();
+
+assert.throws(TypeError, () => {
+ new Intl.DisplayNames('en', options);
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/options-type-valid.js b/js/src/tests/test262/intl402/DisplayNames/options-type-valid.js
new file mode 100644
index 0000000000..54e884e880
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/options-type-valid.js
@@ -0,0 +1,45 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Valid options for localeMatcher
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 4. Let options be ? ToObject(options).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.DisplayNames]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const types = [
+ 'language',
+ 'region',
+ 'script',
+ 'currency'
+];
+
+types.forEach(type => {
+ const obj = new Intl.DisplayNames('en', { type });
+
+ assert(obj instanceof Intl.DisplayNames, `instanceof check - ${type}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.DisplayNames.prototype, `proto check - ${type}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prop-desc.js b/js/src/tests/test262/intl402/DisplayNames/prop-desc.js
new file mode 100644
index 0000000000..337f52b0ab
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prop-desc.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Property descriptor of Intl.DisplayNames
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2
+ has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+ [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+assert.sameValue(typeof Intl.DisplayNames, "function", "`typeof Intl.DisplayNames` is `'function'`");
+
+verifyProperty(Intl, "DisplayNames", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/DisplayNames/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..cac9302e8f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/proto-from-ctor-realm.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames
+description: Default [[Prototype]] value derived from realm of the newTarget
+info: |
+ Intl.DisplayNames ( locales , options )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let displayNames be ? OrdinaryCreateFromConstructor(NewTarget, "%DisplayNamesPrototype%",
+ « [[InitializedDisplayNames]], [[Locale]], [[Style]], [[Type]], [[Fallback]], [[Fields]] »).
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, "prototype").
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.DisplayNames, cross-realm, Reflect]
+---*/
+
+var other = $262.createRealm().global;
+var C = new other.Function();
+C.prototype = null;
+
+var o = Reflect.construct(Intl.DisplayNames, [undefined, {type: 'language'}], C);
+
+assert.sameValue(Object.getPrototypeOf(o), other.Intl.DisplayNames.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/proto.js b/js/src/tests/test262/intl402/DisplayNames/proto.js
new file mode 100644
index 0000000000..7ef279226b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/proto.js
@@ -0,0 +1,15 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ The internal prototype of Intl.DisplayNames
+features: [Intl.DisplayNames]
+---*/
+
+var proto = Object.getPrototypeOf(Intl.DisplayNames);
+
+assert.sameValue(proto, Function.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/Symbol.toStringTag.js b/js/src/tests/test262/intl402/DisplayNames/prototype/Symbol.toStringTag.js
new file mode 100644
index 0000000000..de8db1a805
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/Symbol.toStringTag.js
@@ -0,0 +1,23 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DisplayNames.prototype-@@tostringtag
+description: >
+ Property descriptor of DisplayNames.prototype[@@toStringTag]
+info: |
+ The initial value of the @@toStringTag property is the string value "Intl.DisplayNames".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.DisplayNames.prototype, Symbol.toStringTag, {
+ value: "Intl.DisplayNames",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/browser.js b/js/src/tests/test262/intl402/DisplayNames/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/browser.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/browser.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/shell.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/shell.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-invalid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-invalid.js
new file mode 100644
index 0000000000..1d7fc9ff43
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-invalid.js
@@ -0,0 +1,63 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Throws a RangeError for invalid `calendar` codes
+features: [Intl.DisplayNames-v2]
+---*/
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'calendar'});
+
+assert.throws(RangeError, function() {
+ displayNames.of('00');
+}, 'insufficient length');
+
+assert.throws(RangeError, function() {
+ displayNames.of('000000000');
+}, 'excessive length');
+
+assert.throws(RangeError, function() {
+ displayNames.of('-00000000');
+}, 'leading separator (dash)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('_00000000');
+}, 'leading separator (underscore)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('00000000-');
+}, 'trailing separator (dash)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('00000000_');
+}, 'trailing separator (underscore)');
+
+assert.throws(RangeError, function() {
+ displayNames.of(' abcdef');
+}, 'leading space');
+
+assert.throws(RangeError, function() {
+ displayNames.of('abcdef ');
+}, 'trailing space');
+
+assert.throws(RangeError, function() {
+ displayNames.of('abc def');
+}, 'interstitial space');
+
+assert.throws(RangeError, function() {
+ displayNames.of('123_abc');
+}, '2 segments, minimum length, underscore');
+
+assert.throws(RangeError, function() {
+ displayNames.of('12345678_abcdefgh');
+}, '2 segments, maximum length, underscore');
+
+assert.throws(RangeError, function() {
+ displayNames.of('123_abc_ABC');
+}, '3 segments, minimum length, underscore');
+
+assert.throws(RangeError, function() {
+ displayNames.of('12345678_abcdefgh_ABCDEFGH');
+}, '3 segments, maximum length, underscore');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-valid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-valid.js
new file mode 100644
index 0000000000..9f6bb2a8d1
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-calendar-valid.js
@@ -0,0 +1,29 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Returns string value for valid `calendar` codes
+features: [Intl.DisplayNames-v2]
+---*/
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'calendar'});
+
+assert.sameValue(typeof displayNames.of('01234567'), 'string', '[0-7]');
+assert.sameValue(typeof displayNames.of('899'), 'string', '[89]');
+
+assert.sameValue(typeof displayNames.of('abcdefgh'), 'string', '[a-h]');
+assert.sameValue(typeof displayNames.of('ijklmnop'), 'string', '[i-p]');
+assert.sameValue(typeof displayNames.of('qrstuvwx'), 'string', '[q-x]');
+assert.sameValue(typeof displayNames.of('yzz'), 'string', '[yz]');
+
+assert.sameValue(typeof displayNames.of('ABCDEFGH'), 'string', '[A-H]');
+assert.sameValue(typeof displayNames.of('IJKLMNOP'), 'string', '[I-P]');
+assert.sameValue(typeof displayNames.of('QRSTUVWX'), 'string', '[Q-X]');
+assert.sameValue(typeof displayNames.of('YZZ'), 'string', '[YZ]');
+
+assert.sameValue(typeof displayNames.of('123-abc'), 'string', '2 segments, minimum length, dash');
+assert.sameValue(typeof displayNames.of('12345678-abcdefgh'), 'string', '2 segments, maximum length, dash');
+assert.sameValue(typeof displayNames.of('123-abc-ABC'), 'string', '3 segments, minimum length, dash');
+assert.sameValue(typeof displayNames.of('12345678-abcdefgh-ABCDEFGH'), 'string', '3 segments, maximum length, dash');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-invalid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-invalid.js
new file mode 100644
index 0000000000..b1532a389c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-invalid.js
@@ -0,0 +1,39 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Throws a RangeError for invalid `dateTimeField` codes
+features: [Intl.DisplayNames-v2]
+---*/
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'dateTimeField'});
+
+assert.throws(RangeError, function() {
+ displayNames.of('');
+}, 'empty string');
+
+assert.throws(RangeError, function() {
+ displayNames.of('timezoneName');
+}, 'timezoneName');
+
+assert.throws(RangeError, function() {
+ displayNames.of('timezonename');
+}, 'timezonename');
+
+assert.throws(RangeError, function() {
+ displayNames.of('millisecond');
+}, 'millisecond');
+
+assert.throws(RangeError, function() {
+ displayNames.of('seconds');
+}, 'seconds');
+
+assert.throws(RangeError, function() {
+ displayNames.of(' year');
+}, 'year (with leading space)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('year ');
+}, 'year (with trailing space)');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-valid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-valid.js
new file mode 100644
index 0000000000..d649742ed5
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-datetimefield-valid.js
@@ -0,0 +1,24 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Returns string value for valid `dateTimeField` codes
+features: [Intl.DisplayNames-v2]
+---*/
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'dateTimeField'});
+
+assert.sameValue(typeof displayNames.of('era'), 'string', 'era');
+assert.sameValue(typeof displayNames.of('year'), 'string', 'year');
+assert.sameValue(typeof displayNames.of('quarter'), 'string', 'quarter');
+assert.sameValue(typeof displayNames.of('month'), 'string', 'month');
+assert.sameValue(typeof displayNames.of('weekOfYear'), 'string', 'weekOfYear');
+assert.sameValue(typeof displayNames.of('weekday'), 'string', 'weekday');
+assert.sameValue(typeof displayNames.of('day'), 'string', 'day');
+assert.sameValue(typeof displayNames.of('dayPeriod'), 'string', 'dayPeriod');
+assert.sameValue(typeof displayNames.of('hour'), 'string', 'hour');
+assert.sameValue(typeof displayNames.of('minute'), 'string', 'minute');
+assert.sameValue(typeof displayNames.of('second'), 'string', 'second');
+assert.sameValue(typeof displayNames.of('timeZoneName'), 'string', 'timeZoneName');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-invalid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-invalid.js
new file mode 100644
index 0000000000..e00ed3f1f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-invalid.js
@@ -0,0 +1,90 @@
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Throws a RangeError for invalid `language` codes
+info: |
+ 12.3.3 Intl.DisplayNames.prototype.of ( code )
+
+ 1. If type is "language", then
+ a. If code cannot be matched by the unicode_language_id Unicode locale nonterminal, throw a RangeError exception.
+ b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
+ c. Return CanonicalizeUnicodeLocaleId(code).
+features: [Intl.DisplayNames-v2]
+---*/
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'language'});
+
+assert.throws(RangeError, function() {
+ displayNames.of('');
+}, 'invalid language subtag - empty string');
+
+assert.throws(RangeError, function() {
+ displayNames.of('a');
+}, 'invalid language subtag - only one character');
+
+assert.throws(RangeError, function() {
+ displayNames.of('abcdefghi');
+}, 'invalid language subtag - greater than 8 characters');
+
+assert.throws(RangeError, function() {
+ displayNames.of('en-u-hebrew');
+}, 'singleton subtag');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa-aaaa-bbbb');
+}, 'multiple script subtags');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa-aaaaa-aaaaa');
+}, 'duplicate variant subtag');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa-bb-cc');
+}, 'multiple region subtags');
+
+assert.throws(RangeError, function() {
+ displayNames.of('1a');
+}, 'invalid language subtag - leading digit');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa-1a');
+}, 'leading-digit subtag of length 2');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa-1aa');
+}, 'leading-digit non-numeric subtag of length 3');
+
+assert.throws(RangeError, function() {
+ displayNames.of('@#$%@#$');
+}, 'invalid characters');
+
+assert.throws(RangeError, function() {
+ displayNames.of('en-US-');
+}, 'separator not followed by subtag');
+
+assert.throws(RangeError, function() {
+ displayNames.of('-en');
+}, 'separator at start');
+
+assert.throws(RangeError, function() {
+ displayNames.of('en--GB');
+}, 'missing subtag between separators');
+
+assert.throws(RangeError, function() {
+ displayNames.of('root');
+}, 'BCP 47-incompatible CLDR syntax ("root" instead of "und")');
+
+assert.throws(RangeError, function(){
+ displayNames.of('abcd-GB');
+}, 'BCP 47-incompatible CLDR syntax (script subtag without a language subtag)');
+
+assert.throws(RangeError, function(){
+ displayNames.of('abcd');
+}, 'BCP 47-incompatible CLDR syntax (bare script subtag)');
+
+assert.throws(RangeError, function(){
+ displayNames.of('en_GB');
+}, 'BCP 47-incompatible CLDR syntax (_ as separator)');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-valid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-valid.js
new file mode 100644
index 0000000000..2b7932bf90
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-language-valid.js
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Returns string value for valid `language` codes
+features: [Intl.DisplayNames]
+---*/
+
+
+/*
+unicode_language_id = "root" // not allowed in ES
+ | (unicode_language_subtag
+ (sep unicode_script_subtag)?
+ | unicode_script_subtag)
+ (sep unicode_region_subtag)?
+ (sep unicode_variant_subtag)*
+ */
+
+// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
+
+var languages = [ { subtag: 'ab', description: '2 letter language_subtag' },
+ { subtag: 'cde', description: '3 letter language_subtag' },
+ { subtag: 'zzzzzzzz', description: '8 letter language_subtag'} ];
+
+// unicode_script_subtag = alpha{4} ;
+var scripts = [ {subtag: 'abcd', description: '4 letter script_subtag' },
+ {subtag: '', description: ''} ];
+
+// unicode_region_subtag = (alpha{2} | digit{3}) ;
+var regions = [ {subtag: 'ab', description: '2 letter region_subtag' },
+ {subtag: '123', description: '3 digit region_subtag'},
+ {subtag: '', description: ''} ];
+
+// unicode_variant_subtag = (alphanum{5, 8} | digit alphanum{3}
+
+var variants = [ {subtag: 'abcde', description: '5 letter variant_subtag'},
+ {subtag: 'fghijklm', description: '8 letter variant_subtag'},
+ {subtag: '12345', description: '5 digit variant_subtag'},
+ {subtag: '1nopq', description: '5 chararcter leading digit variant_subtag'},
+ {subtag: '12345678', description: '8 digit variant_subtag'},
+ {subtag: 'a2345678', description: '8 character trailing digit variant_subtag'},
+ {subtag: '1abc', description: 'leading digit 4 character variant_subtag' },
+ {subtag: '2345', description: '4 digit variant_subtag'},
+ {subtag: '6d7e', description: 'leading digit 4 character mixed alphanum variant_subtag'},
+ {subtag: '', description: ''} ];
+
+
+function notEmpty(subtag) {
+ return subtag !== '';
+}
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'language'});
+
+for (var l in languages) {
+ for (var s in scripts) {
+ for (var r in regions) {
+ for (var v in variants) {
+ var languageTag = [languages[l].subtag, scripts[s].subtag, regions[r].subtag, variants[v].subtag].filter(notEmpty).join('-');
+ var languageDescription = [languages[l].description, scripts[s].description, regions[r].description, variants[v].description].filter(notEmpty).join(', ');
+
+ assert.sameValue(typeof displayNames.of(languageTag), 'string', languageDescription + ": " + languageTag);
+ if (variants[v].subtag !== ''){
+ for (var vAdditional in variants){
+ if (variants[vAdditional].subtag !== '' && vAdditional !== v){
+ languageTag += '-' + variants[vAdditional].subtag;
+ languageDescription += ", " + variants[vAdditional].description;
+ assert.sameValue(typeof displayNames.of(languageTag), 'string', languageDescription + ": " + languageTag);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-region-invalid.js b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-region-invalid.js
new file mode 100644
index 0000000000..0fcce4ac7e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/of/type-region-invalid.js
@@ -0,0 +1,85 @@
+// Copyright 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.of
+description: Throws a RangeError for invalid `region` codes
+info: |
+ 12.5.1 CanonicalCodeForDisplayNames ( code )
+
+ ...
+ 2. If type is "region", then
+ a. If code cannot be matched by the unicode_region_subtag Unicode locale nonterminal, throw a RangeError exception.
+ b. Return the ASCII-uppercase of code.
+features: [Intl.DisplayNames]
+---*/
+
+// https://unicode.org/reports/tr35/#unicode_region_subtag
+// unicode_region_subtag = (alpha{2} | digit{3}) ;
+
+var displayNames = new Intl.DisplayNames(undefined, {type: 'region'});
+
+assert.throws(RangeError, function() {
+ displayNames.of('00');
+}, 'insufficient length, numeric');
+
+assert.throws(RangeError, function() {
+ displayNames.of('a');
+}, 'insufficient length, alpha');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aaa');
+}, 'excessive length, alpha');
+
+assert.throws(RangeError, function() {
+ displayNames.of('1111');
+}, 'excessive length, numeric');
+
+assert.throws(RangeError, function() {
+ displayNames.of('');
+}, 'empty string');
+
+assert.throws(RangeError, function() {
+ displayNames.of('a01');
+}, 'mixed alphanumeric (alpha first, length 3)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('a1');
+}, 'mixed alphanumeric (alpha first, length 2)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('1a');
+}, 'mixed alphanumeric (numeric first, length 2)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('1a1');
+}, 'mixed alphanumeric (numeric first, length 3)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('-111');
+}, 'leading separator (dash)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('_111');
+}, 'leading separator (underscore)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('111-');
+}, 'trailing separator (dash)');
+
+assert.throws(RangeError, function() {
+ displayNames.of('111-');
+}, 'trailing separator (underscore)');
+
+assert.throws(RangeError, function() {
+ displayNames.of(' aa');
+}, 'leading space');
+
+assert.throws(RangeError, function() {
+ displayNames.of('aa ');
+}, 'trailing space');
+
+assert.throws(RangeError, function() {
+ displayNames.of('a c');
+}, 'interstitial space');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/prop-desc.js b/js/src/tests/test262/intl402/DisplayNames/prototype/prop-desc.js
new file mode 100644
index 0000000000..1c35488ac3
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/prop-desc.js
@@ -0,0 +1,22 @@
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype
+description: >
+ Property descriptor of Intl.DisplayNames.prototype
+info: |
+ The value of Intl.DisplayNames.prototype is %DisplayNamesPrototype%.
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+verifyProperty(Intl.DisplayNames, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/default-option-values.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/default-option-values.js
new file mode 100644
index 0000000000..96475e2ded
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/default-option-values.js
@@ -0,0 +1,82 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Default values for each option
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+
+ Intl.DisplayNames ( locales , options )
+
+ ...
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
+ %DisplayNames%.[[RelevantExtensionKeys]]).
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+ 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames]
+includes: [propertyHelper.js]
+---*/
+
+var dn = new Intl.DisplayNames('en-US', {type: 'language'});
+
+var options = dn.resolvedOptions();
+
+verifyProperty(options, 'style', {
+ value: 'long',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+verifyProperty(options, 'type', {
+ value: 'language',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+verifyProperty(options, 'fallback', {
+ value: 'code',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..402df82db0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/length.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Intl.DisplayNames.prototype.resolvedOptions.length is 0.
+info: |
+ ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+verifyProperty(Intl.DisplayNames.prototype.resolvedOptions, "length", {
+ value: 0,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..91f54d44cc
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Intl.DisplayNames.prototype.resolvedOptions.name is "resolvedOptions".
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value is a
+ String.
+
+ Unless otherwise specified, the name property of a built-in Function object,
+ if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]:
+ false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+verifyProperty(Intl.DisplayNames.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-fallback.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-fallback.js
new file mode 100644
index 0000000000..2fe0d03843
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-fallback.js
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Values for the fallback option
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+ [[LanguageDisplay]]: "languageDisplay"
+
+ Intl.DisplayNames ( locales , options )
+
+ ...
+ 10. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
+ %DisplayNames%.[[RelevantExtensionKeys]]).
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+ 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames]
+includes: [propertyHelper.js]
+---*/
+
+const fallbacks = ['code', 'none'];
+const types = ['language', 'region', 'script', 'currency'];
+
+types.forEach(type => {
+ fallbacks.forEach(fallback => {
+ const dn = new Intl.DisplayNames('en-US', { fallback, type });
+ const options = dn.resolvedOptions();
+
+ verifyProperty(options, 'fallback', {
+ value: fallback,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'type', {
+ value: type,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'style', {
+ value: 'long',
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-languagedisplay.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-languagedisplay.js
new file mode 100644
index 0000000000..3b3317a9d6
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-languagedisplay.js
@@ -0,0 +1,70 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: Values for the languageDisplay option
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+ [[LanguageDisplay]]: "languageDisplay"
+
+ Intl.DisplayNames ( locales , options )
+
+ ...
+ 24. Let languageDisplay be ? GetOption(options, "languageDisplay", "string",
+ « "dialect", "standard" », "dialect").
+ 25. If type is "language", then
+ a. Set displayNames.[[LanguageDisplay]] to languageDisplay.
+ b. Let typeFields be typeFields.[[<languageDisplay>]].
+ c. Assert: typeFields is a Record (see 1.4.3).
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames-v2]
+includes: [propertyHelper.js]
+---*/
+
+var dn;
+
+dn = new Intl.DisplayNames('en-US', { type: 'language', languageDisplay: 'dialect' });
+
+verifyProperty(dn.resolvedOptions(), 'languageDisplay', {
+ value: 'dialect',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+dn = new Intl.DisplayNames('en-US', { type: 'language', languageDisplay: 'standard' });
+
+verifyProperty(dn.resolvedOptions(), 'languageDisplay', {
+ value: 'standard',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-style.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-style.js
new file mode 100644
index 0000000000..12961ca43e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-style.js
@@ -0,0 +1,89 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Values for the style option
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+ [[LanguageDisplay]]: "languageDisplay"
+
+ Intl.DisplayNames ( locales , options )
+
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
+ %DisplayNames%.[[RelevantExtensionKeys]]).
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+ 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames]
+includes: [propertyHelper.js]
+---*/
+
+const styles = ['narrow', 'short', 'long'];
+const types = ['language', 'region', 'script', 'currency'];
+
+types.forEach(type => {
+ styles.forEach(style => {
+ const dn = new Intl.DisplayNames('en-US', { style, type });
+ const options = dn.resolvedOptions();
+
+ verifyProperty(options, 'style', {
+ value: style,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'type', {
+ value: type,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'fallback', {
+ value: 'code',
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-type.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-type.js
new file mode 100644
index 0000000000..5bb0254f0e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/option-type.js
@@ -0,0 +1,86 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Values for the type option
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+ [[LanguageDisplay]]: "languageDisplay"
+
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 10. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
+ %DisplayNames%.[[RelevantExtensionKeys]]).
+ 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 13. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency"»,
+ "language").
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+ 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames]
+includes: [propertyHelper.js]
+---*/
+
+var types = ['language', 'region', 'script', 'currency'];
+
+types.forEach(type => {
+ var dn = new Intl.DisplayNames('en-US', { type });
+ var options = dn.resolvedOptions();
+
+ verifyProperty(options, 'type', {
+ value: type,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'fallback', {
+ value: 'code',
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+
+ verifyProperty(options, 'style', {
+ value: 'long',
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..1766f5a5ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Property descriptor of Intl.DisplayNames.prototype.resolvedOptions
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2
+ has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+ [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.DisplayNames]
+---*/
+
+assert.sameValue(
+ typeof Intl.DisplayNames.prototype.resolvedOptions,
+ "function",
+ "`typeof Intl.DisplayNames.prototype.resolvedOptions` is `'function'`"
+);
+
+verifyProperty(Intl.DisplayNames.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/return-object.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/return-object.js
new file mode 100644
index 0000000000..9f5d2f8e3d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/return-object.js
@@ -0,0 +1,100 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Returns a new ordinary object on each call, with data properties containing values from internals
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 6, except the header row, in table order, do
+ a. Let p be the Property value of the current row.
+ b. Let v be the value of pr's internal slot whose name is the Internal Slot value of the current row.
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+ 6. Return options.
+
+ Table 6: Resolved Options of DisplayNames Instances
+
+ [[Locale]]: "locale"
+ [[Style]]: "style"
+ [[Type]]: "type"
+ [[Fallback]]: "fallback"
+ [[LanguageDisplay]]: "languageDisplay"
+
+ Intl.DisplayNames ( locales , options )
+
+ ...
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+ 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
+ %DisplayNames%.[[RelevantExtensionKeys]]).
+ 10. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
+ ...
+ 12. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" », undefined).
+ 13. If type is undefined, throw a TypeError exception.
+ ...
+ 15. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
+ ...
+ 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].
+ ...
+
+ CreateDataProperty ( O, P, V )
+
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true,
+ [[Configurable]]: true }.
+ ...
+locale: [en-US]
+features: [Intl.DisplayNames-v2, Reflect]
+includes: [propertyHelper.js, compareArray.js]
+---*/
+
+const dn = new Intl.DisplayNames('en-US', {type: 'language'});
+
+const options = dn.resolvedOptions();
+const other = dn.resolvedOptions();
+
+assert.notSameValue(options, other, 'each call returns a new object');
+
+assert.sameValue(Object.getPrototypeOf(options), Object.prototype, 'ordinary object #1');
+assert.sameValue(Object.getPrototypeOf(other), Object.prototype, 'ordinary object #2');
+
+assert.compareArray(
+ Reflect.ownKeys(options),
+ ['locale', 'style', 'type', 'fallback', 'languageDisplay'],
+ 'all the data properties set to this object, in order of creation'
+);
+
+verifyProperty(options, 'locale', {
+ value: 'en-US',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+const explicit = new Intl.DisplayNames(
+ 'en', { localeMatcher: 'lookup', type: 'language' }).resolvedOptions();
+
+assert.sameValue(
+ explicit.hasOwnProperty('localeMatcher'),
+ false,
+ 'the localeMatcher option is not set, option was explicitly set'
+);
+
+const extra = new Intl.DisplayNames(
+ 'en', { chaos: 'yes', random: 'sure', '0': 42, type: 'language' }).resolvedOptions();
+
+assert.compareArray(
+ Reflect.ownKeys(extra),
+ ['locale', 'style', 'type', 'fallback', 'languageDisplay'],
+ 'extra properties are not reflected in the resolvedOptions'
+);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/shell.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-not-object-throws.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-not-object-throws.js
new file mode 100644
index 0000000000..42cac5976e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-not-object-throws.js
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Throws a TypeError if this is not Object.
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ ...
+features: [Intl.DisplayNames, Symbol]
+---*/
+
+var resolvedOptions = Intl.DisplayNames.prototype.resolvedOptions;
+
+assert.throws(TypeError, function() {
+ resolvedOptions();
+}, 'direct call');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call('en');
+}, 'string');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(1);
+}, 'number');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(null);
+}, 'null');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(true);
+}, 'true');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(false);
+}, 'false');
+
+var symbol = Symbol();
+assert.throws(TypeError, function() {
+ resolvedOptions.call(symbol);
+}, 'symbol');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-object-lacks-internal-throws.js b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-object-lacks-internal-throws.js
new file mode 100644
index 0000000000..9ad4c1517b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/resolvedOptions/this-object-lacks-internal-throws.js
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames.prototype.resolvedOptions
+description: >
+ Throws a TypeError if this does not have an [[InitializedDisplayNames]] internal slot.
+info: |
+ Intl.DisplayNames.prototype.resolvedOptions ()
+
+ 1. Let pr be the this value.
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedDisplayNames]] internal slot,
+ throw a TypeError exception.
+ ...
+features: [Intl.DisplayNames]
+---*/
+
+var resolvedOptions = Intl.DisplayNames.prototype.resolvedOptions;
+
+assert.throws(TypeError, function() {
+ Intl.DisplayNames.prototype.resolvedOptions();
+}, 'Intl.DisplayNames.prototype does not have the internal slot');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call({});
+}, 'ordinary object');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(Intl.DisplayNames);
+}, 'Intl.DisplayNames does not have the internal slot');
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(Intl);
+}, 'Intl does not have the internal slot');
+
+// Not DisplayNames!!!
+var dtf = new Intl.DateTimeFormat();
+
+assert.throws(TypeError, function() {
+ resolvedOptions.call(dtf);
+}, 'resolvedOptions cannot be used with instances from different Intl ctors');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DisplayNames/prototype/shell.js b/js/src/tests/test262/intl402/DisplayNames/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/shell.js b/js/src/tests/test262/intl402/DisplayNames/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/shell.js
diff --git a/js/src/tests/test262/intl402/DisplayNames/undefined-newtarget-throws.js b/js/src/tests/test262/intl402/DisplayNames/undefined-newtarget-throws.js
new file mode 100644
index 0000000000..3b28c14486
--- /dev/null
+++ b/js/src/tests/test262/intl402/DisplayNames/undefined-newtarget-throws.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DisplayNames
+description: >
+ Throws a TypeError if Intl.DisplayNames is called as a function.
+info: |
+ Intl.DisplayNames ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ ...
+features: [Intl.DisplayNames]
+---*/
+
+assert.throws(TypeError, function() {
+ Intl.DisplayNames();
+});
+
+assert.throws(TypeError, function() {
+ Intl.DisplayNames('en');
+});
+
+assert.throws(TypeError, function() {
+ Intl.DisplayNames(['en']);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/browser.js b/js/src/tests/test262/intl402/DurationFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-locales-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-locales-invalid.js
new file mode 100644
index 0000000000..687ae01c37
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-locales-invalid.js
@@ -0,0 +1,20 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks error cases for the locales argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, function() { new Intl.DurationFormat(locales) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-locales-valid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-locales-valid.js
new file mode 100644
index 0000000000..6d58367d43
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-locales-valid.js
@@ -0,0 +1,37 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks cases for the locales argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+features: [Intl.DurationFormat]
+---*/
+
+const defaultLocale = new Intl.DurationFormat().resolvedOptions().locale;
+
+const matchers = ["lookup", "best fit"]
+
+const tests = [
+ [undefined, defaultLocale, "undefined"],
+ ["EN", "en", "Single value"],
+ [[], defaultLocale, "Empty array"],
+ [["en", "EN"], "en", "Duplicate value (canonical first)"],
+ [["EN", "en"], "en", "Duplicate value (canonical last)"],
+ [{ 0: "DE", length: 0 }, defaultLocale, "Object with zero length"],
+ [{ 0: "DE", length: 1 }, "de", "Object with length"],
+];
+
+
+for (const [locales, expected, name] of tests) {
+ matchers.forEach((matcher)=>{
+ const drf = new Intl.DurationFormat(locales, {localeMatcher: matcher});
+ assert.sameValue(drf.resolvedOptions().locale, expected, name);
+ });
+};
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-defaults.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-defaults.js
new file mode 100644
index 0000000000..90c8c62a28
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-defaults.js
@@ -0,0 +1,36 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks handling of valid options for the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 17. For each row in Table 1, except the header row, in table order, do
+ a. Let styleSlot be the Style Slot value.
+ b. Let displaySlot be the Display Slot value.
+ c. Let unit be the Unit value.
+ d. Let valueList be the Values value.
+ e. Let digitalBase be the Digital Default value.
+ f. Let unitOptions be ? GetUnitOptions(unit, options, style, valueList, digitalBase, prevStyle).
+ g. Set durationFormat.[[<styleSlot>]] to unitOptions.[[Style]].
+ h. Set durationFormat.[[<displaySlot>]] to unitOptions.[[Display]].
+features: [Intl.DurationFormat]
+includes: [testIntl.js]
+---*/
+
+testOption( Intl.DurationFormat, "years", "string", ["long", "short", "narrow"], "short");
+testOption( Intl.DurationFormat, "months", "string", ["long", "short", "narrow"], "short");
+testOption( Intl.DurationFormat, "weeks", "string", ["long", "short", "narrow"], "short");
+testOption( Intl.DurationFormat, "days", "string", ["long", "short", "narrow"], "short");
+testOption( Intl.DurationFormat, "hours", "string", ["long", "short", "narrow", "numeric", "2-digit"], "short");
+testOption( Intl.DurationFormat, "minutes", "string", ["long", "short", "narrow", "numeric", "2-digit"], "short");
+testOption( Intl.DurationFormat, "seconds", "string", ["long", "short", "narrow", "numeric", "2-digit"], "short");
+testOption( Intl.DurationFormat, "milliseconds", "string", ["long", "short", "narrow", "numeric"], "short");
+testOption( Intl.DurationFormat, "microseconds", "string", ["long", "short", "narrow", "numeric"], "short");
+testOption( Intl.DurationFormat, "nanoseconds", "string", ["long", "short", "narrow", "numeric"], "short");
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-invalid.js
new file mode 100644
index 0000000000..7f580a8e32
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-invalid.js
@@ -0,0 +1,26 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Tests that the option localeMatcher is processed correctly.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 18. Set durationFormat.[[FractionalDigits]] to ? GetNumberOption(options, "fractionalDigits", 0, 9, undefined).
+features: [Intl.DurationFormat]
+---*/
+
+const invalidOptions = [
+ -10,
+ 10
+];
+
+for (const fractionalDigits of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat("en", { fractionalDigits });
+ }, `new Intl.DurationFormat("en", {fractionalDigits: "${fractionalDigits}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-valid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-valid.js
new file mode 100644
index 0000000000..183ebdafed
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-fractionalDigits-valid.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Tests that the option localeMatcher is processed correctly.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 18. Set durationFormat.[[FractionalDigits]] to ? GetNumberOption(options, "fractionalDigits", 0, 9, undefined).
+features: [Intl.DurationFormat]
+---*/
+
+const validOptions = [
+ 0,
+ 1,
+ 5,
+ 9,
+ undefined
+];
+
+for (const fractionalDigits of validOptions) {
+ const obj = new Intl.DurationFormat("en", {fractionalDigits});
+ assert.sameValue(obj.resolvedOptions().fractionalDigits, fractionalDigits, `${fractionalDigits} is supported by DurationFormat`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-invalid.js
new file mode 100644
index 0000000000..26b0c361fa
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-invalid.js
@@ -0,0 +1,19 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks handling of a null options argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 4. Let options be GetOptionsObject(options).
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat, "function");
+
+assert.throws(TypeError, function() { new Intl.DurationFormat([], null) })
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..ed89c0787f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-invalid.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks handling of invalid value for the localeMatcher option to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+features: [Intl.DurationFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const localeMatcher of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat([], { localeMatcher });
+ }, `${localeMatcher} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-valid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-valid.js
new file mode 100644
index 0000000000..0190aabe73
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-localeMatcher-valid.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Tests that the option localeMatcher is processed correctly.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+features: [Intl.DurationFormat]
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.DurationFormat, "localeMatcher", "string", ["lookup", "best fit"], "best fit", {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-invalid.js
new file mode 100644
index 0000000000..eac66df308
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-invalid.js
@@ -0,0 +1,39 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: >
+ Checks error cases for the options argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
+ 7. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+const invalidOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-latn-",
+ "latn-",
+ "latn--",
+ "latn-ca",
+ "latn-ca-",
+ "latn-ca-gregory",
+ "latné",
+ "latn编号",
+];
+for (const numberingSystem of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat('en', {numberingSystem});
+ }, `new Intl.DurationFormat("en", {numberingSystem: "${numberingSystem}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-valid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-valid.js
new file mode 100644
index 0000000000..c053bba51a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-numberingSystem-valid.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: >
+ Checks error cases for the options argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
+ 7. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+for (const numberingSystem of numberingSystems) {
+ const obj = new Intl.DurationFormat("en", {numberingSystem});
+ assert.sameValue(obj.resolvedOptions().numberingSystem, numberingSystem, `${numberingSystem} is supported by DurationFormat`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-order.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-order.js
new file mode 100644
index 0000000000..073a62fb5c
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-order.js
@@ -0,0 +1,44 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks the order of operations on the options argument to the DurationFormat constructor.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+ (...)
+ 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
+ 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow", "digital" », "long").
+includes: [compareArray.js]
+features: [Intl.DurationFormat]
+---*/
+
+var actual = [];
+
+const options = {
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return undefined;
+ },
+ get numberingSystem() {
+ actual.push("numberingSystem");
+ return undefined;
+ },
+ get style() {
+ actual.push("style");
+ return undefined;
+ },
+};
+
+const expected = [
+ "localeMatcher",
+ "numberingSystem",
+ "style"
+];
+
+let nf = new Intl.DurationFormat(undefined, options);
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-conflict.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-conflict.js
new file mode 100644
index 0000000000..4ad190c997
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-conflict.js
@@ -0,0 +1,46 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-isvaliddurationrecord
+description: Checks that the "long", "short", and "narrow" styles can't be used for units following a unit using the "numeric" or "2-digit" style.
+info: |
+ GetDurationUnitOptions (unit, options, baseStyle, stylesList, digitalBase, prevStyle)
+ (...)
+ 6. If prevStyle is "numeric" or "2-digit", then
+ a. If style is not "numeric" or "2-digit", then
+ i. Throw a RangeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+let invalidOptions = {};
+for (const timeUnit of ["hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds"]){
+ invalidOptions[timeUnit] = "numeric";
+}
+
+for (const timeUnit of ["minutes", "seconds", "milliseconds", "microseconds", "nanoseconds"]){
+ for (const invalidStyle of ["long", "short", "narrow"]){
+ invalidOptions[timeUnit] = invalidStyle;
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat([], invalidOptions);
+ }, `${invalidStyle} is an invalid style option value when following a unit with \"numeric\" style`);
+ }
+ invalidOptions[timeUnit] = "numeric";
+}
+
+for (const timeUnit of ["hours", "minutes", "seconds"]){
+ invalidOptions[timeUnit] = "2-digit";
+}
+
+for (const timeUnit of ["minutes", "seconds", "milliseconds"]){
+ for (const invalidStyle of ["long", "short", "narrow"]){
+ invalidOptions[timeUnit] = invalidStyle;
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat([], invalidOptions);
+ }, `${invalidStyle} is an invalid style option value when following a unit with \"2-digit\" style`);
+ }
+ invalidOptions[timeUnit] = "2-digit";
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-invalid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-invalid.js
new file mode 100644
index 0000000000..7b8bcced9d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-invalid.js
@@ -0,0 +1,40 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks handling of invalid value for the style option to the DurationFormat constructor.
+info: |
+ InitializeDurationFormat (DurationFormat, locales, options)
+ (...)
+ 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow", "digital" », "long").
+ 14. Set durationFormat.[[Style]] to style.
+features: [Intl.DurationFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Long",
+ "LONG",
+ "long\0",
+ "Short",
+ "SHORT",
+ "short\0",
+ "Narrow",
+ "NARROW",
+ "narrow\0",
+ "Digital",
+ "DIGITAL",
+ "digital\0",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.DurationFormat([], {"style": invalidOption});
+ }, `${invalidOption} is an invalid style option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-valid.js b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-valid.js
new file mode 100644
index 0000000000..03ebfe4d2d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/constructor-options-style-valid.js
@@ -0,0 +1,34 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Checks handling of valid values for the style option to the DurationFormat constructor.
+info: |
+ InitializeDurationFormat (DurationFormat, locales, options)
+ (...)
+ 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow", "digital" », "short").
+ 14. Set durationFormat.[[Style]] to style.
+features: [Intl.DurationFormat]
+---*/
+
+const validOptions = [
+ [undefined, "short"],
+ ["long", "long"],
+ ["short", "short"],
+ ["narrow", "narrow"],
+ ["digital", "digital"],
+ [{ toString() { return "short"; } }, "short"],
+ [{ toString() { return "long"; } }, "long"],
+ [{ toString() { return "narrow"; } }, "narrow"],
+ [{ toString() { return "digital"; } }, "digital"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const df = new Intl.DurationFormat([], {"style": validOption});
+ const resolvedOptions = df.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/extensibility.js b/js/src/tests/test262/intl402/DurationFormat/extensibility.js
new file mode 100644
index 0000000000..f0d7dcc554
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/extensibility.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Intl.DurationFormat instance object extensibility
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Unless specified otherwise, the [[Extensible]] internal slot
+ of a built-in object initially has the value true.
+
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(
+ Object.isExtensible(new Intl.DurationFormat()),
+ true,
+ "Object.isExtensible(new Intl.DurationFormat()) returns true"
+);
+
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/length.js b/js/src/tests/test262/intl402/DurationFormat/length.js
new file mode 100644
index 0000000000..f59faedd88
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/length.js
@@ -0,0 +1,36 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat
+description: >
+ Intl.DurationFormat.length is 0.
+info: |
+ Intl.DurationFormat ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat, 'length', {
+ value: 0,
+ enumerable: false,
+ writable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/name.js b/js/src/tests/test262/intl402/DurationFormat/name.js
new file mode 100644
index 0000000000..1dd1d72c2d
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/name.js
@@ -0,0 +1,32 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: >
+ Intl.DurationFormat.name is "DurationFormat".
+info: |
+ Intl.DurationFormat ([ locales [ , options ]])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat, 'name', {
+ value: 'DurationFormat',
+ enumerable: false,
+ writable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/newtarget-undefined.js b/js/src/tests/test262/intl402/DurationFormat/newtarget-undefined.js
new file mode 100644
index 0000000000..5cafac6772
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/newtarget-undefined.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: >
+ Verifies the NewTarget check for Intl.DurationFormat.
+info: |
+ Intl.DurationFormat ([ locales [ , options ]])
+ (...)
+ 1. If NewTarget is undefined, throw a TypeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat, "function");
+
+assert.throws(TypeError, function() {
+ Intl.DurationFormat();
+});
+
+assert.throws(TypeError, function() {
+ Intl.DurationFormat("en");
+});
+
+assert.throws(TypeError, function() {
+ Intl.DurationFormat("not-valid-tag");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/prop-desc.js
new file mode 100644
index 0000000000..4f5ad92f25
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prop-desc.js
@@ -0,0 +1,35 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat
+description: >
+ "DurationFormat" property of Intl.
+info: |
+ Intl.DurationFormat ([ locales [ , options ]])
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, 'DurationFormat', {
+ enumerable: false,
+ writable: true,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype.js b/js/src/tests/test262/intl402/DurationFormat/prototype.js
new file mode 100644
index 0000000000..69ba3ef085
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat
+description: Intl.DurationFormat instance object is created from %DurationFormatPrototype%.
+info: |
+ Intl.DurationFormat ([ locales [ , options ]])
+
+ 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]] , [[WeeksStyle]], [[WeeksDisplay]] , [[DaysStyle]], [[DaysDisplay]] , [[HoursStyle]], [[HoursDisplay]] , [[MinutesStyle]], [[MinutesDisplay]] , [[SecondsStyle]], [[SecondsDisplay]] , [[MillisecondsStyle]], [[MillisecondsDisplay]] , [[MicrosecondsStyle]], [[MicrosecondsDisplay]] , [[NanosecondsStyle]], [[NanosecondsDisplay]], [[FractionalDigits]] »).
+
+features: [Intl.DurationFormat]
+---*/
+
+const value = new Intl.DurationFormat();
+assert.sameValue(
+ Object.getPrototypeOf(value),
+ Intl.DurationFormat.prototype,
+ "Object.getPrototypeOf(value) equals the value of Intl.DurationFormat.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..f0f66bafa2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/prop-desc.js
@@ -0,0 +1,35 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat
+description: >
+ "constructor" property of Intl.DurationFormat.prototype.
+info: |
+ Intl.DurationFormat.prototype.constructor
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype, "constructor", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/value.js b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/value.js
new file mode 100644
index 0000000000..c984b7829e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/constructor/value.js
@@ -0,0 +1,15 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat
+description: >
+ Tests that Intl.DurationFormat.prototype.constructor is the Intl.DurationFormat.
+
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(Intl.DurationFormat.prototype.constructor, Intl.DurationFormat, "Intl.DurationFormat.prototype.constructor is not the same as Intl.DurationFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/branding.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/branding.js
new file mode 100644
index 0000000000..acaccd554b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Verifies the branding check for the "format" function of the DurationFormat prototype object.
+features: [Intl.DurationFormat]
+---*/
+
+const format = Intl.DurationFormat.prototype.format;
+
+assert.sameValue(typeof format, "function");
+
+assert.throws(TypeError, () => format.call(undefined, { years : 2 }), "undefined");
+assert.throws(TypeError, () => format.call(null, { years : 2 }), "null");
+assert.throws(TypeError, () => format.call(true, { years : 2 }), "true");
+assert.throws(TypeError, () => format.call("", { years : 2 }), "empty string");
+assert.throws(TypeError, () => format.call(Symbol(), { years : 2 }), "symbol");
+assert.throws(TypeError, () => format.call(1, { years : 2 }), "1");
+assert.throws(TypeError, () => format.call({}, { years : 2 }), "plain object");
+assert.throws(TypeError, () => format.call(Intl.DurationFormat, { years : 2 } ), "Intl.DurationFormat");
+assert.throws(TypeError, () => format.call(Intl.DurationFormat.prototype, { years : 2 }), "Intl.DurationFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-arguments-throws.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-arguments-throws.js
new file mode 100644
index 0000000000..ed9c268b63
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-arguments-throws.js
@@ -0,0 +1,31 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ "format" basic tests for invalid arguments that should throw TypeError exception.
+info: |
+ Intl.DurationFormat.prototype.format(duration)
+ (...)
+ 3. Let record be ? ToDurationRecord(duration)
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+
+assert.throws(TypeError, () => { df.format(undefined) }, "undefined" );
+assert.throws(TypeError, () => { df.format(null) }, "null");
+assert.throws(TypeError, () => { df.format(true) }, "true");
+assert.throws(TypeError, () => { df.format(-12) }, "-12");
+assert.throws(TypeError, () => { df.format(-12n) }, "-12n");
+assert.throws(TypeError, () => { df.format(1) }, "1");
+assert.throws(TypeError, () => { df.format(2n) }, "2n");
+assert.throws(TypeError, () => { df.format({}) }, "plain object");
+assert.throws(TypeError, () => { df.format({ year: 1 }) }, "unsuported property");
+assert.throws(TypeError, () => { df.format({ years: undefined }) }, "supported property set undefined");
+assert.throws(TypeError, () => { df.format(Symbol())}, "symbol");
+assert.throws(RangeError, () => { df.format("bad string")}, "bad string");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-negative-duration-throws.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-negative-duration-throws.js
new file mode 100644
index 0000000000..b349ee52f7
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/invalid-negative-duration-throws.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: format basic tests for invalid negative duration objects that should throw RangeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+
+
+const df = new Intl.DurationFormat();
+
+assert.throws(RangeError, () => { df.format({
+ hours : -1,
+ minutes: 10
+}), "Throws when mixing negative and positive values" });
+
+assert.throws(RangeError, () => { df.format({
+ hours : 2,
+ minutes: -10
+}), "Throws when mixing negative and positive values" });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/length.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/length.js
new file mode 100644
index 0000000000..073a761a32
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/length.js
@@ -0,0 +1,37 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Intl.DurationFormat.prototype.format.length is 1.
+info: |
+ Intl.DurationFormat.prototype.format ( duration )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype.format, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/name.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/name.js
new file mode 100644
index 0000000000..57a2250fd2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/name.js
@@ -0,0 +1,29 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Checks the "name" property of Intl.DurationFormat.prototype.format().
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype.format, "name", {
+ value: "format",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-default-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-default-en.js
new file mode 100644
index 0000000000..c6b58a5dfc
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-default-en.js
@@ -0,0 +1,36 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Test format method with negative duration and default style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -3,
+ hours: -4,
+ minutes: -5,
+ seconds: -6,
+ milliseconds: -7,
+ microseconds: -8,
+ nanoseconds: -9,
+};
+
+const expected = formatDurationFormatPattern(duration);
+
+const df = new Intl.DurationFormat("en");
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `DurationFormat format output using default style option`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-short-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-short-en.js
new file mode 100644
index 0000000000..83eb5b4639
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-duration-style-short-en.js
@@ -0,0 +1,38 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Test format method with negative duration and "short" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "short";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -3,
+ hours: -4,
+ minutes: -5,
+ seconds: -6,
+ milliseconds: -7,
+ microseconds: -8,
+ nanoseconds: -9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `DurationFormat format output using ${style} style option`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-digital-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-digital-en.js
new file mode 100644
index 0000000000..c9f119c368
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-digital-en.js
@@ -0,0 +1,38 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Test format method with negative duration and "digital" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "digital";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -3,
+ hours: -4,
+ minutes: -5,
+ seconds: -6,
+ milliseconds: -7,
+ microseconds: -8,
+ nanoseconds: -9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `DurationFormat format output using ${style} style option`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-long-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-long-en.js
new file mode 100644
index 0000000000..fe074d488e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-long-en.js
@@ -0,0 +1,38 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Test format method with negative duration and "long" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "long";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -3,
+ hours: -4,
+ minutes: -5,
+ seconds: -6,
+ milliseconds: -7,
+ microseconds: -8,
+ nanoseconds: -9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `DurationFormat format output using ${style} style option`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-narrow-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-narrow-en.js
new file mode 100644
index 0000000000..4d1db5dc67
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/negative-durationstyle-narrow-en.js
@@ -0,0 +1,38 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Test format method with negative duration and "narrow" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "narrow";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -3,
+ hours: -4,
+ minutes: -5,
+ seconds: -6,
+ milliseconds: -7,
+ microseconds: -8,
+ nanoseconds: -9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `DurationFormat format output using ${style} style option`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/not-a-constructor.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/not-a-constructor.js
new file mode 100644
index 0000000000..a029decc62
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/not-a-constructor.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ Intl.DurationFormat.prototype.format does not implement [[Construct]], is not new-able
+info: |
+ Built-in function objects that are not identified as constructors do not implement the
+ [[Construct]] internal method unless otherwise specified in the description of a particular
+ function.
+includes: [isConstructor.js]
+features: [Reflect.construct, Intl.DurationFormat]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.DurationFormat.prototype.format();
+}, "Calling as constructor");
+
+assert.sameValue(isConstructor(Intl.DurationFormat.prototype.format), false,
+ "isConstructor(Intl.DurationFormat.prototype.format)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/numeric-hour-with-zero-minutes-and-non-zero-seconds.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/numeric-hour-with-zero-minutes-and-non-zero-seconds.js
new file mode 100644
index 0000000000..dc5ad4ea84
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/numeric-hour-with-zero-minutes-and-non-zero-seconds.js
@@ -0,0 +1,44 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ The correct separator is used for numeric hours with zero minutes and non-zero seconds.
+locale: [en-US]
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat("en", {
+ // hours must be numeric, so that a time separator is used for the following units.
+ hours: "numeric",
+});
+
+const lf = new Intl.ListFormat("en", {
+ type: "unit",
+ style: "short",
+});
+
+const duration = {
+ hours: 1,
+
+ // Minutes is omitted from the output when its value is zero.
+ minutes: 0,
+
+ // Either seconds or sub-seconds must be non-zero.
+ seconds: 3,
+};
+
+const expected = lf.format([
+ new Intl.NumberFormat("en", {minimumIntegerDigits: 1}).format(duration.hours),
+ new Intl.NumberFormat("en", {minimumIntegerDigits: 2}).format(duration.seconds),
+]);
+
+assert.sameValue(
+ df.format(duration),
+ expected,
+ `No time separator is used when minutes is zero`
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js
new file mode 100644
index 0000000000..5518450d6a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js
@@ -0,0 +1,96 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: >
+ PartitionDurationFormatPattern computes on exact mathematical values.
+info: |
+ PartitionDurationFormatPattern ( durationFormat, duration )
+ ...
+ 4. While done is false, repeat for each row in Table 1 in order, except the header row:
+ ...
+ j. If unit is "seconds", "milliseconds", or "microseconds", then
+ i. If unit is "seconds", then
+ 1. Let nextStyle be durationFormat.[[MillisecondsStyle]].
+ ...
+ iv. If nextStyle is "numeric", then
+ 1. If unit is "seconds", then
+ a. Set value to value + duration.[[Milliseconds]] / 10^3 + duration.[[Microseconds]] / 10^6 + duration.[[Nanoseconds]] / 10^9.
+ ...
+ l. If value is not 0 or display is not "auto", then
+ ii. If style is "2-digit" or "numeric", then
+ ...
+ 7. Let parts be ! PartitionNumberPattern(nf, value).
+ ...
+
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const durations = [
+ // 10000000 + (1 / 10^9)
+ // = 10000000.000000001
+ {
+ seconds: 10_000_000,
+ nanoseconds: 1,
+ },
+
+ // 9007199254740991 + (9007199254740991 / 10^3) + (9007199254740991 / 10^6) + (9007199254740991 / 10^9)
+ // = 9.016215470202185986731991 × 10^15
+ {
+ seconds: Number.MAX_SAFE_INTEGER,
+ milliseconds: Number.MAX_SAFE_INTEGER,
+ microseconds: Number.MAX_SAFE_INTEGER,
+ nanoseconds: Number.MAX_SAFE_INTEGER,
+ },
+ {
+ seconds: Number.MIN_SAFE_INTEGER,
+ milliseconds: Number.MIN_SAFE_INTEGER,
+ microseconds: Number.MIN_SAFE_INTEGER,
+ nanoseconds: Number.MIN_SAFE_INTEGER,
+ },
+
+ // 1 + (2 / 10^3) + (3 / 10^6) + (9007199254740991 / 10^9)
+ // = 9.007200256743991 × 10^6
+ {
+ seconds: 1,
+ milliseconds: 2,
+ microseconds: 3,
+ nanoseconds: Number.MAX_SAFE_INTEGER,
+ },
+
+ // 9007199254740991 + (10^3 / 10^3) + (10^6 / 10^6) + (10^9 / 10^9)
+ // = 9007199254740991 + 3
+ // = 9007199254740994
+ {
+ seconds: Number.MAX_SAFE_INTEGER,
+ milliseconds: 10 ** 3,
+ microseconds: 10 ** 6,
+ nanoseconds: 10 ** 9,
+ },
+
+ // ~1.7976931348623157e+308 / 10^9
+ // = ~1.7976931348623157 × 10^299
+ {
+ seconds: 0,
+ milliseconds: 0,
+ microseconds: 0,
+ nanoseconds: Number.MAX_VALUE,
+ },
+];
+
+const df = new Intl.DurationFormat("en", {style: "digital"});
+
+for (let duration of durations) {
+ let expected = formatDurationFormatPattern(duration, "digital");
+ assert.sameValue(
+ df.format(duration),
+ expected,
+ `Duration is ${JSON.stringify(duration)}`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/prop-desc.js
new file mode 100644
index 0000000000..ebb842ae83
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Property descriptor of Intl.DurationFormat.prototype.format
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.DurationFormat.prototype.format,
+ 'function',
+ '`typeof Intl.DurationFormat.prototype.format` is `function`'
+);
+
+verifyProperty(Intl.DurationFormat.prototype, 'format', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-default-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-default-en.js
new file mode 100644
index 0000000000..6eb5c4999e
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-default-en.js
@@ -0,0 +1,31 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Test if format method formats duration correctly with different "style" arguments
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const duration = {
+ years: 1,
+ months: 2,
+ weeks: 3,
+ days: 3,
+ hours: 4,
+ minutes: 5,
+ seconds: 6,
+ milliseconds: 7,
+ microseconds: 8,
+ nanoseconds: 9,
+};
+
+const expected = formatDurationFormatPattern(duration);
+
+const df = new Intl.DurationFormat("en");
+assert.sameValue(df.format(duration), expected, `Assert DurationFormat format output using default style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-en.js
new file mode 100644
index 0000000000..4f34d50aa9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-en.js
@@ -0,0 +1,34 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// Copyright 2023 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Test if format method formats duration correctly with different "style" arguments
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "digital";
+
+const duration = {
+ years: 1,
+ months: 2,
+ weeks: 3,
+ days: 3,
+ hours: 4,
+ minutes: 5,
+ seconds: 6,
+ milliseconds: 7,
+ microseconds: 8,
+ nanoseconds: 9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(df.format(duration), expected, `Assert DurationFormat format output using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits-undefined.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits-undefined.js
new file mode 100644
index 0000000000..631e6c36eb
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits-undefined.js
@@ -0,0 +1,71 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondurationformatpattern
+description: >
+ Test to ensure that correct number of fractional digits is displayed (i.e. however many are necessary to represent the data fully) if the fractionalDigits option is left *undefined*
+
+info: |
+ 4. If durationFormat.[[FractionalDigits]] is undefined, then
+ a. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", 9).
+ b. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", 0).
+ 5. Else,
+ a. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", durationFormat.[[FractionalDigits]]).
+ b. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", durationFormat.[[FractionalDigits]]).
+features: [Intl.DurationFormat]
+---*/
+
+
+const durationNano = {
+ hours: 1,
+ minutes: 22,
+ seconds: 33,
+ milliseconds: 111,
+ microseconds: 222,
+ nanoseconds: 333
+};
+
+const durationMicro = {
+ hours: 1,
+ minutes: 22,
+ seconds: 33,
+ milliseconds: 111,
+ microseconds: 222
+};
+
+const durationMill = {
+ hours: 1,
+ minutes: 22,
+ seconds: 33,
+ milliseconds: 111
+};
+
+const durationNoSubsecond = {
+ hours: 1,
+ minutes: 22,
+ seconds: 33
+};
+
+const durationSevenFractional = {
+ hours: 2,
+ minutes: 30,
+ seconds: 10,
+ milliseconds: 111,
+ microseconds: 220,
+ nanoseconds: 300
+};
+
+const style = "digital";
+const df = new Intl.DurationFormat(undefined, {style, fractionalDigits: undefined});
+
+assert.sameValue(df.format(durationNano), "1:22:33.111222333", `format output with nanosecond digits and fractionalDigits: undefined using ${style} style option`);
+assert.sameValue(df.format(durationMicro), "1:22:33.111222", `format output with microsecond digits and fractionalDigits: undefined using ${style} style option`);
+assert.sameValue(df.format(durationMilli), "1:22:33.111", `format output with millisecond digits and fractionalDigits: undefined using ${style} style option`);
+assert.sameValue(df.format(durationNoSubsecond), "1:22:33", `format output with no subsecond digits and fractionalDigits: undefined using ${style} style option`);
+
+assert.sameValue(df.format(durationFiveFractional), "2:30:11122", `format output with five subsecond digits and fractionalDigits: undefined using ${style} style option`);
+assert.sameValue(df.format(durationSevenFractional), "2:30:1112203", `format output with seven subsecond digits and fractionalDigits: undefined using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits.js
new file mode 100644
index 0000000000..d75f51b14a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-digital-fractionalDigits.js
@@ -0,0 +1,50 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-partitiondurationformatpattern
+description: >
+ Test to ensure that correct number of fractional digits is displayed if fractionalDigits is explicitly specified.
+
+info: |
+ 4. If durationFormat.[[FractionalDigits]] is undefined, then
+ a. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", 9).
+ b. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", 0).
+ 5. Else,
+ a. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", durationFormat.[[FractionalDigits]]).
+ b. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", durationFormat.[[FractionalDigits]]).
+features: [Intl.DurationFormat]
+---*/
+
+const duration = {
+ hours: 1,
+ minutes: 22,
+ seconds: 33,
+ milliseconds: 111,
+ microseconds: 222,
+ nanoseconds: 333,
+};
+
+
+const style = "digital";
+const df = new Intl.DurationFormat(undefined, {style, fractionalDigits: 0});
+const dfMilli = new Intl.DurationFormat(undefined, {style, fractionalDigits: 3});
+const dfFourDigits = new Intl.DurationFormat(undefined, {style, fractionalDigits: 4});
+const dfMicro = new Intl.DurationFormat(undefined, {style, fractionalDigits: 6});
+const dfEightDigits = new Intl.DurationFormat(undefined, {style, fractionalDigits: 8});
+const dfNano = new Intl.DurationFormat(undefined, {style, fractionalDigits: 9});
+
+assert.sameValue(df.format(duration), "1:22:33", `format output without sub-second digits using ${style} style option`);
+
+assert.sameValue(dfMilli.format(duration), "1:22:33.111", `format output with sub-second digits and fractionalDigits: 3 using ${style} style option`);
+
+assert.sameValue(dfFourDigits.format(duration), "1:22:33.1112", `format output with sub-second digits and fractionalDigits: 4 using ${style} style option`);
+
+assert.sameValue(dfMicro.format(duration), "1:22:33.111222", `format output with sub-second digits and fractionalDigits: 6 using ${style} style option`);
+
+assert.sameValue(dfEightDigits.format(duration), "1:22:33.11122233", `format output with sub-second digits and fractionalDigits: 8 using ${style} style option`);
+
+assert.sameValue(dfNano.format(duration), "1:22:33.111222333", `format output with sub-second digits and fractionalDigits: 9 using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-long-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-long-en.js
new file mode 100644
index 0000000000..d9e6fc6cbb
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-long-en.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Test if format method formats duration correctly with different "style" arguments
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "long";
+
+const duration = {
+ years: 1,
+ months: 2,
+ weeks: 3,
+ days: 3,
+ hours: 4,
+ minutes: 5,
+ seconds: 6,
+ milliseconds: 7,
+ microseconds: 8,
+ nanoseconds: 9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(df.format(duration), expected, `Assert DurationFormat format output using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-narrow-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-narrow-en.js
new file mode 100644
index 0000000000..66b2258761
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-narrow-en.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Test if format method formats duration correctly with different "style" arguments
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "narrow";
+
+const duration = {
+ years: 1,
+ months: 2,
+ weeks: 3,
+ days: 3,
+ hours: 4,
+ minutes: 5,
+ seconds: 6,
+ milliseconds: 7,
+ microseconds: 8,
+ nanoseconds: 9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(df.format(duration), expected, `Assert DurationFormat format output using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-short-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-short-en.js
new file mode 100644
index 0000000000..2a66c01b06
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/style-short-en.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.format
+description: Test if format method formats duration correctly with different "style" arguments
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+const style = "short";
+
+const duration = {
+ years: 1,
+ months: 2,
+ weeks: 3,
+ days: 3,
+ hours: 4,
+ minutes: 5,
+ seconds: 6,
+ milliseconds: 7,
+ microseconds: 8,
+ nanoseconds: 9,
+};
+
+const expected = formatDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", {style});
+assert.sameValue(df.format(duration), expected, `Assert DurationFormat format output using ${style} style option`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/format/throw-invoked-as-func.js b/js/src/tests/test262/intl402/DurationFormat/prototype/format/throw-invoked-as-func.js
new file mode 100644
index 0000000000..1e59d7c6d9
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/format/throw-invoked-as-func.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat.prototype.format
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.DurationFormat.prototype.format ( duration )
+ (...)
+ 2. Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]).
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+
+let f = df["format"];
+
+assert.sameValue(typeof f, "function");
+assert.throws(TypeError, () => {
+ f({ hours: 1, minutes: 46, seconds: 40 });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/branding.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/branding.js
new file mode 100644
index 0000000000..179fdcb064
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Verifies the branding check for the "formatToParts" function of the DurationFormat prototype object.
+features: [Intl.DurationFormat]
+---*/
+
+const formatToParts = Intl.DurationFormat.prototype.formatToParts;
+
+assert.sameValue(typeof formatToParts, "function");
+
+assert.throws(TypeError, () => formatToParts.call(undefined, { years : 2 }), "undefined");
+assert.throws(TypeError, () => formatToParts.call(null, { years : 2 }), "null");
+assert.throws(TypeError, () => formatToParts.call(true, { years : 2 }), "true");
+assert.throws(TypeError, () => formatToParts.call("", { years : 2 }), "empty string");
+assert.throws(TypeError, () => formatToParts.call(Symbol(), { years : 2 }), "symbol");
+assert.throws(TypeError, () => formatToParts.call(1, { years : 2 }), "1");
+assert.throws(TypeError, () => formatToParts.call({}, { years : 2 }), "plain object");
+assert.throws(TypeError, () => formatToParts.call(Intl.DurationFormat, { years : 2 } ), "Intl.DurationFormat");
+assert.throws(TypeError, () => formatToParts.call(Intl.DurationFormat.prototype, { years : 2 }), "Intl.DurationFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-default-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-default-en.js
new file mode 100644
index 0000000000..03b16dc627
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-default-en.js
@@ -0,0 +1,47 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks basic handling of formatToParts, using long, short,narrow and digital styles.
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ if (expectedEntry.unit) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+const duration = {
+ hours: 7,
+ minutes: 8,
+ seconds: 9,
+ milliseconds: 123,
+ microseconds: 456,
+ nanoseconds: 789,
+};
+
+const expected = partitionDurationFormatPattern(duration);
+
+let df = new Intl.DurationFormat('en');
+compare(df.formatToParts(duration), expected, `Using style : default`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-digital-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-digital-en.js
new file mode 100644
index 0000000000..0e8246f212
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-digital-en.js
@@ -0,0 +1,49 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks basic handling of formatToParts, using long, short,narrow and digital styles.
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ if (expectedEntry.unit) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+const duration = {
+ hours: 7,
+ minutes: 8,
+ seconds: 9,
+ milliseconds: 123,
+ microseconds: 456,
+ nanoseconds: 789,
+};
+
+const style = "digital";
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+let df = new Intl.DurationFormat('en', { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-long-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-long-en.js
new file mode 100644
index 0000000000..a54ddb61f8
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-long-en.js
@@ -0,0 +1,49 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks basic handling of formatToParts, using long, short,narrow and digital styles.
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ if (expectedEntry.unit) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+const duration = {
+ hours: 7,
+ minutes: 8,
+ seconds: 9,
+ milliseconds: 123,
+ microseconds: 456,
+ nanoseconds: 789,
+};
+
+const style = "long";
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+let df = new Intl.DurationFormat('en', { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-narrow-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-narrow-en.js
new file mode 100644
index 0000000000..feff8fda60
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-narrow-en.js
@@ -0,0 +1,49 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks basic handling of formatToParts, using long, short,narrow and digital styles.
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ if (expectedEntry.unit) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+const duration = {
+ hours: 7,
+ minutes: 8,
+ seconds: 9,
+ milliseconds: 123,
+ microseconds: 456,
+ nanoseconds: 789,
+};
+
+const style = "narrow";
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+let df = new Intl.DurationFormat('en', { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-short-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-short-en.js
new file mode 100644
index 0000000000..605448bde0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/formatToParts-style-short-en.js
@@ -0,0 +1,49 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks basic handling of formatToParts, using long, short,narrow and digital styles.
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ if (expectedEntry.unit) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+const duration = {
+ hours: 7,
+ minutes: 8,
+ seconds: 9,
+ milliseconds: 123,
+ microseconds: 456,
+ nanoseconds: 789,
+};
+
+const style = "short";
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+let df = new Intl.DurationFormat('en', { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-arguments-throws.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-arguments-throws.js
new file mode 100644
index 0000000000..7f3105086f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-arguments-throws.js
@@ -0,0 +1,40 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ "formatToParts" basic tests for invalid arguments that should throw TypeError exception.
+info: |
+ Intl.DurationFormat.prototype.formatToParts(duration)
+ (...)
+ 3. Let record be ? ToDurationRecord(duration)
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+const testOptions = [ "years", "months", "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds"];
+
+assert.throws(TypeError, () => { df.formatToParts(undefined) }, "undefined" );
+assert.throws(TypeError, () => { df.formatToParts(null) }, "null");
+assert.throws(TypeError, () => { df.formatToParts(true) }, "true");
+assert.throws(TypeError, () => { df.formatToParts(-12) }, "-12");
+assert.throws(TypeError, () => { df.formatToParts(-12n) }, "-12n");
+assert.throws(TypeError, () => { df.formatToParts(1) }, "1");
+assert.throws(TypeError, () => { df.formatToParts(2n) }, "2n");
+assert.throws(TypeError, () => { df.formatToParts({}) }, "plain object");
+assert.throws(TypeError, () => { df.formatToParts({ year: 1 }) }, "unsuported property");
+assert.throws(TypeError, () => { df.formatToParts({ years: undefined }) }, "supported property set undefined");
+assert.throws(TypeError, () => { df.formatToParts(Symbol())}, "symbol");
+assert.throws(RangeError, () => { df.formatToParts("bad string")}, "bad string");
+
+testOptions.forEach( option => {
+ assert.throws(RangeError, () => { df.formatToParts({ [option]: 2.5 })}, " duration properties must be integers");
+});
+
+testOptions.forEach( option => {
+ assert.throws(RangeError, () => { df.formatToParts({ [option]: -Infinity })}, " duration properties must be integers");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-negative-duration-throws.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-negative-duration-throws.js
new file mode 100644
index 0000000000..ede7dd8839
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/invalid-negative-duration-throws.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: formatToParts basic tests for invalid negative duration objects that should throw RangeError exception.
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+
+assert.throws(RangeError, () => { df.formatToParts({
+ hours : -1,
+ minutes: 10
+}), "Throws when mixing negative and positive values" });
+
+assert.throws(RangeError, () => { df.formatToParts({
+ hours : 2,
+ minutes: -10
+}), "Throws when mixing negative and positive values" });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/length.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/length.js
new file mode 100644
index 0000000000..c131c7e239
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/length.js
@@ -0,0 +1,39 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.formatToParts
+description: >
+ Intl.DateTimeFormat.prototype.formatToParts.length is 1.
+info: |
+ Intl.DateTimeFormat.prototype.formatToParts ( date )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(Intl.DateTimeFormat.prototype.formatToParts.length, 1);
+
+verifyProperty(Intl.DurationFormat.prototype.formatToParts, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/name.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/name.js
new file mode 100644
index 0000000000..dc21910462
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/name.js
@@ -0,0 +1,29 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Checks the "name" property of Intl.DurationFormat.prototype.formatToParts().
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype.formatToParts, "name", {
+ value: "formatToParts",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-default-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-default-en.js
new file mode 100644
index 0000000000..bfc182c34b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-default-en.js
@@ -0,0 +1,50 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Test formatToParts method with negative duration and default style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < expected.length; ++i) {
+ let actualEntry = actual[i];
+ let expectedEntry = expected[i];
+
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue("unit" in actualEntry, "unit" in expectedEntry, `unit for entry ${i}`);
+ if ("unit" in expectedEntry) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -4,
+ hours: -5,
+ minutes: -6,
+ seconds: -7,
+ milliseconds: -123,
+ microseconds: -456,
+ nanoseconds: -789,
+};
+
+const expected = partitionDurationFormatPattern(duration);
+
+const df = new Intl.DurationFormat("en");
+compare(df.formatToParts(duration), expected, `Using style : default`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-digital-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-digital-en.js
new file mode 100644
index 0000000000..52a40072ba
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-digital-en.js
@@ -0,0 +1,52 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Test formatToParts method with negative duration and "digital" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < expected.length; ++i) {
+ let actualEntry = actual[i];
+ let expectedEntry = expected[i];
+
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue("unit" in actualEntry, "unit" in expectedEntry, `unit for entry ${i}`);
+ if ("unit" in expectedEntry) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+
+const style = "digital";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -4,
+ hours: -5,
+ minutes: -6,
+ seconds: -7,
+ milliseconds: -123,
+ microseconds: -456,
+ nanoseconds: -789,
+};
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-long-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-long-en.js
new file mode 100644
index 0000000000..3756db8a84
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-long-en.js
@@ -0,0 +1,52 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Test formatToParts method with negative duration and "long" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < expected.length; ++i) {
+ let actualEntry = actual[i];
+ let expectedEntry = expected[i];
+
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue("unit" in actualEntry, "unit" in expectedEntry, `unit for entry ${i}`);
+ if ("unit" in expectedEntry) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+
+const style = "long";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -4,
+ hours: -5,
+ minutes: -6,
+ seconds: -7,
+ milliseconds: -123,
+ microseconds: -456,
+ nanoseconds: -789,
+};
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-narrow-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-narrow-en.js
new file mode 100644
index 0000000000..a9717ce67a
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-narrow-en.js
@@ -0,0 +1,52 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Test formatToParts method with negative duration and "narrow" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < expected.length; ++i) {
+ let actualEntry = actual[i];
+ let expectedEntry = expected[i];
+
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue("unit" in actualEntry, "unit" in expectedEntry, `unit for entry ${i}`);
+ if ("unit" in expectedEntry) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+
+const style = "narrow";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -4,
+ hours: -5,
+ minutes: -6,
+ seconds: -7,
+ milliseconds: -123,
+ microseconds: -456,
+ nanoseconds: -789,
+};
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-short-en.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-short-en.js
new file mode 100644
index 0000000000..bc2ed69c5b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/negative-duration-formatToParts-style-short-en.js
@@ -0,0 +1,52 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2023 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Test formatToParts method with negative duration and "short" style
+locale: [en-US]
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+function compare(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < expected.length; ++i) {
+ let actualEntry = actual[i];
+ let expectedEntry = expected[i];
+
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue("unit" in actualEntry, "unit" in expectedEntry, `unit for entry ${i}`);
+ if ("unit" in expectedEntry) {
+ assert.sameValue(actualEntry.unit, expectedEntry.unit, `unit for entry ${i}`);
+ }
+ }
+}
+
+const style = "short";
+
+const duration = {
+ years: -1,
+ months: -2,
+ weeks: -3,
+ days: -4,
+ hours: -5,
+ minutes: -6,
+ seconds: -7,
+ milliseconds: -123,
+ microseconds: -456,
+ nanoseconds: -789,
+};
+
+const expected = partitionDurationFormatPattern(duration, style);
+
+const df = new Intl.DurationFormat("en", { style });
+compare(df.formatToParts(duration), expected, `Using style : ${style}`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/not-a-constructor.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/not-a-constructor.js
new file mode 100644
index 0000000000..b845cabe35
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/not-a-constructor.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: >
+ Intl.DurationFormat.prototype.formatToParts does not implement [[Construct]], is not new-able
+info: |
+ Built-in function objects that are not identified as constructors do not implement the
+ [[Construct]] internal method unless otherwise specified in the description of a particular
+ function.
+includes: [isConstructor.js]
+features: [Reflect.construct, Intl.DurationFormat]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.DurationFormat.prototype.formatToParts();
+}, "Calling as constructor");
+
+assert.sameValue(isConstructor(Intl.DurationFormat.prototype.formatToParts), false,
+ "isConstructor(Intl.DurationFormat.prototype.formatToParts)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/prop-desc.js
new file mode 100644
index 0000000000..3c32931386
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.formatToParts
+description: Property descriptor of Intl.DurationFormat.prototype.formatToParts
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.DurationFormat.prototype.formatToParts,
+ 'function',
+ '`typeof Intl.DurationFormat.prototype.formatToParts` is `function`'
+);
+
+verifyProperty(Intl.DurationFormat.prototype, 'formatToParts', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/throw-invoked-as-func.js b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/throw-invoked-as-func.js
new file mode 100644
index 0000000000..d091b49dd2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/formatToParts/throw-invoked-as-func.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat.prototype.format
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.DurationFormat.prototype.formatToParts ( duration )
+ (...)
+ 2. Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]).
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+
+// Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]).
+let f = df["formatToParts"];
+
+assert.sameValue(typeof f, "function");
+assert.throws(TypeError, () => {
+ f({ hours: 1, minutes: 46, seconds: 40 });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/prototype_attributes.js b/js/src/tests/test262/intl402/DurationFormat/prototype/prototype_attributes.js
new file mode 100644
index 0000000000..34e2511dda
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/prototype_attributes.js
@@ -0,0 +1,21 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2021 Nikhil Singhal. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype
+description: Prototype attributes verification
+info: |
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat, "prototype", {
+ value: Intl.DurationFormat.prototype,
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..f59ec0500f
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/length.js
@@ -0,0 +1,39 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat.prototype.resolvedoptions
+description: >
+ Intl.DurationFormat.prototype.resolvedOptions.length is 0.
+info: |
+ Intl.DurationFormat.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+features: [Intl.DurationFormat]
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(Intl.DurationFormat.prototype.resolvedOptions.length, 0);
+
+verifyProperty(Intl.DurationFormat.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..dbab5aaa2b
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/name.js
@@ -0,0 +1,29 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.resolvedOptions
+description: Checks the "name" property of Intl.DurationFormat.prototype.resolvedOptions().
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..1b667908c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.prototype.resolvedOptions
+description: Property descriptor of Intl.DurationFormat.prototype.resolvedOptions
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.DurationFormat.prototype.resolvedOptions,
+ 'function',
+ '`typeof Intl.DurationFormat.prototype.resolvedOptions` is `function`'
+);
+
+verifyProperty(Intl.DurationFormat.prototype, 'resolvedOptions', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/shell.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/throw-invoked-as-func.js b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/throw-invoked-as-func.js
new file mode 100644
index 0000000000..a75c9f2bb4
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/resolvedOptions/throw-invoked-as-func.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat.prototype.resolvedOptions
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.DurationFormat.prototype.resolvedOptions ( )
+ (...)
+ 2. Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]).
+features: [Intl.DurationFormat]
+---*/
+
+const df = new Intl.DurationFormat();
+
+// Perform ? RequireInternalSlot(df, [[InitializedDurationFormat]]).
+let f = df['resolvedOptions'];
+
+assert.sameValue(typeof f, 'function');
+assert.throws(TypeError, () => { f() });
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..b02fe53967
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toString.js
@@ -0,0 +1,20 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.DurationFormat.prototype-@@tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.DurationFormat.prototype[@@toStringTag].
+info: |
+ Intl.DurationFormat.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.DurationFormat".
+
+features: [Intl.DurationFormat, Symbol.toStringTag]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.DurationFormat.prototype), "[object Intl.DurationFormat]");
+assert.sameValue(Object.prototype.toString.call(new Intl.DurationFormat()), "[object Intl.DurationFormat]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..f8cd2882d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright (C) 2021 Nikhil Singhal. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+// Copyright 2022 Igalia, S.L. All rights reserved.
+
+/*---
+esid: sec-intl.DurationFormat.prototype-@@tostringtag
+description: >
+ Property descriptor of Intl.DurationFormat.prototype[@@toStringTag].
+info: |
+ Intl.DurationFormat.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.DurationFormat".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+
+features: [Intl.DurationFormat, Symbol.toStringTag]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.DurationFormat.prototype, Symbol.toStringTag, {
+ value: "Intl.DurationFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/shell.js b/js/src/tests/test262/intl402/DurationFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/shell.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..300e1d24ee
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/basic.js
@@ -0,0 +1,22 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: Tests that Intl.DurationFormat has a supportedLocalesOf property, and it works as expected.
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat.supportedLocalesOf, "function",
+ "supportedLocalesOf should be supported.");
+
+const defaultLocale = new Intl.DurationFormat().resolvedOptions().locale;
+const notSupported = "zxx"; // "no linguistic content"
+const requestedLocales = [defaultLocale, notSupported];
+
+const supportedLocales = Intl.DurationFormat.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, "The length of the supported locales list should be 1");
+assert.sameValue(supportedLocales[0], defaultLocale, "The default locale is returned in the supported list.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/branding.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/branding.js
new file mode 100644
index 0000000000..d8ab61d338
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/branding.js
@@ -0,0 +1,35 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: >
+ Verifies there's no branding check for Intl.DurationFormat.supportedLocalesOf().
+info: |
+ Intl.DurationFormat.supportedLocalesOf ( locales [, options ])
+features: [Intl.DurationFormat]
+---*/
+
+const supportedLocalesOf = Intl.DurationFormat.supportedLocalesOf;
+
+assert.sameValue(typeof supportedLocalesOf, "function");
+
+const thisValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.DurationFormat,
+ Intl.DurationFormat.prototype,
+];
+
+for (const thisValue of thisValues) {
+ const result = supportedLocalesOf.call(thisValue);
+ assert.sameValue(Array.isArray(result), true);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..b3c6b3e1d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/length.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: >
+ Checks the "length" property of Intl.DurationFormat.supportedLocalesOf().
+info: |
+ The value of the length property of the supportedLocalesOf method is 1.
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, has a length property whose value is an integer.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+verifyProperty(Intl.DurationFormat.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-empty.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-empty.js
new file mode 100644
index 0000000000..14fc1b0494
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-empty.js
@@ -0,0 +1,22 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: Checks handling of an empty locales argument to the supportedLocalesOf function.
+info: |
+ Intl.DurationFormat.supportedLocalesOf ( locales [, options ])
+ (...)
+ 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
+includes: [compareArray.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat.supportedLocalesOf, "function",
+ "Should support Intl.DurationFormat.supportedLocalesOf.");
+
+assert.compareArray(Intl.DurationFormat.supportedLocalesOf(), []);
+assert.compareArray(Intl.DurationFormat.supportedLocalesOf([]), []);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-invalid.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-invalid.js
new file mode 100644
index 0000000000..8f5e8c0e81
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-invalid.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: Checks error cases for the locales argument to the supportedLocalesOf function.
+info: |
+ Intl.DurationFormat.supportedLocalesOf ( locales [, options ])
+ (...)
+ 2. Let requestedLocales be CanonicalizeLocaleList(locales).
+includes: [testIntl.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat.supportedLocalesOf, "function",
+ "Should support Intl.DurationFormat.supportedLocalesOf.");
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, () => Intl.DurationFormat.supportedLocalesOf(locales));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-specific.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-specific.js
new file mode 100644
index 0000000000..77696d7ef0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/locales-specific.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: Checks handling of specific locales arguments to the supportedLocalesOf function.
+info: |
+ Intl.DurationFormat.supportedLocalesOf ( locales [, options ])
+ (...)
+ 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
+includes: [compareArray.js]
+locale: [sr, sr-Thai-RS, de, zh-CN]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(typeof Intl.DurationFormat.supportedLocalesOf, "function",
+ "Should support Intl.DurationFormat.supportedLocalesOf.");
+
+assert.compareArray(Intl.DurationFormat.supportedLocalesOf("sr"), ["sr"]);
+
+const multiLocale = ["sr-Thai-RS", "de", "zh-CN"];
+assert.compareArray(Intl.DurationFormat.supportedLocalesOf(multiLocale, {localeMatcher: "lookup"}), multiLocale);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..dc452628d0
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/name.js
@@ -0,0 +1,24 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: >
+ Checks the "name" property of Intl.DurationFormat.supportedLocalesOf().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+verifyProperty(Intl.DurationFormat.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..ff212a9704
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,32 @@
+// |reftest| skip -- Intl.DurationFormat is not supported
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.DurationFormat.supportedLocalesOf
+description: >
+ Checks the "supportedLocalesOf" property of the DurationFormat prototype object.
+info: |
+ Intl.DurationFormat.supportedLocalesOf ( locales [, options ])
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.DurationFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.DurationFormat.supportedLocalesOf,
+ "function",
+ "typeof Intl.DurationFormat.supportedLocalesOf is function"
+);
+
+verifyProperty(Intl.DurationFormat, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/DurationFormat/supportedLocalesOf/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/browser.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/browser.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/browser.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/fails-on-distinct-temporal-types.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/fails-on-distinct-temporal-types.js
new file mode 100644
index 0000000000..9bac489052
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/fails-on-distinct-temporal-types.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.formatRange
+description: formatRange fails if given arguments of different Temporal types
+features: [Temporal]
+---*/
+
+const us = new Intl.DateTimeFormat('en-US');
+
+const instances = {
+ date: new Date(1580527800000),
+ instant: new Temporal.Instant(0n),
+ plaindate: new Temporal.PlainDate(2000, 5, 2),
+ plaindatetime: new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321),
+ plainmonthday: new Temporal.PlainMonthDay(5, 2),
+ plaintime: new Temporal.PlainTime(13, 37),
+ plainyearmonth: new Temporal.PlainYearMonth(2019, 6),
+ zoneddatetime: new Temporal.ZonedDateTime(0n, 'America/Kentucky/Louisville')
+};
+
+Object.entries(instances).forEach(([typeName, instance]) => {
+ Object.entries(instances).forEach(([anotherTypeName, anotherInstance]) => {
+ if (typeName !== anotherTypeName) {
+ assert.throws(
+ TypeError,
+ () => { us.formatRange(instance, anotherInstance); },
+ 'formatRange: bad arguments (' + typeName + ' and ' + anotherTypeName + ')'
+ );
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/shell.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRange/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/browser.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/fails-on-distinct-temporal-types.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/fails-on-distinct-temporal-types.js
new file mode 100644
index 0000000000..8fcc1a54e2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/fails-on-distinct-temporal-types.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.datetimeformat.prototype.formatRangeToParts
+description: formatRange fails if given arguments of different Temporal types
+features: [Temporal]
+---*/
+
+const us = new Intl.DateTimeFormat('en-US');
+
+const instances = {
+ date: new Date(1580527800000),
+ instant: new Temporal.Instant(0n),
+ plaindate: new Temporal.PlainDate(2000, 5, 2),
+ plaindatetime: new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321),
+ plainmonthday: new Temporal.PlainMonthDay(5, 2),
+ plaintime: new Temporal.PlainTime(13, 37),
+ plainyearmonth: new Temporal.PlainYearMonth(2019, 6),
+ zoneddatetime: new Temporal.ZonedDateTime(0n, 'America/Kentucky/Louisville')
+};
+
+Object.entries(instances).forEach(([typeName, instance]) => {
+ Object.entries(instances).forEach(([anotherTypeName, anotherInstance]) => {
+ if (typeName !== anotherTypeName) {
+ assert.throws(
+ TypeError,
+ () => { us.formatRangeToParts(instance, anotherInstance); },
+ 'formatRange: bad arguments (' + typeName + ' and ' + anotherTypeName + ')'
+ );
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/shell.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/formatRangeToParts/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/shell.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/DateTimeFormat/shell.js b/js/src/tests/test262/intl402/Intl/DateTimeFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/DateTimeFormat/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/browser.js b/js/src/tests/test262/intl402/Intl/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/builtin.js b/js/src/tests/test262/intl402/Intl/builtin.js
new file mode 100644
index 0000000000..7641f06945
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/builtin.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: intl-object
+description: >
+ Tests that Intl meets the requirements for built-in objects
+ defined by the introduction of chapter 17 of the ECMAScript
+ Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert(Object.isExtensible(Intl), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl), Object.prototype,
+ "The [[Prototype]] of Intl is %ObjectPrototype%.");
+
+assert.sameValue(this.Intl, Intl,
+ "%Intl% is accessible as a property of the global object.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/Locale-object.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/Locale-object.js
new file mode 100644
index 0000000000..49ed15f8cc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/Locale-object.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests for Locale objects in the argument to getCanonicalLocales
+info: |
+ CanonicalizeLocaleList ( locales )
+ 7. c. iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
+ 1. Let tag be kValue.[[Locale]].
+includes: [compareArray.js]
+features: [Intl.Locale]
+---*/
+
+assert.compareArray(Intl.getCanonicalLocales([
+ "fr-CA",
+ new Intl.Locale("en-gb-oxendict"),
+ "de",
+ new Intl.Locale("jp", { "calendar": "gregory" }),
+ "zh",
+ new Intl.Locale("fr-CA"),
+]), [
+ "fr-CA",
+ "en-GB-oxendict",
+ "de",
+ "jp-u-ca-gregory",
+ "zh",
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/browser.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-tags.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-tags.js
new file mode 100644
index 0000000000..f80e448086
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-tags.js
@@ -0,0 +1,66 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Call Intl.getCanonicalLocales function with valid language tags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ a. Let Pk be ToString(k).
+ b. Let kPresent be ? HasProperty(O, Pk).
+ c. If kPresent is true, then
+ i. Let kValue be ? Get(O, Pk).
+ ...
+ iii. Let tag be ? ToString(kValue).
+ ...
+ v. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
+ vi. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
+ ...
+includes: [testIntl.js]
+---*/
+
+var canonicalizedTags = {
+ "de": "de",
+ "DE-de": "de-DE",
+ "de-DE": "de-DE",
+ "cmn": "zh",
+ "CMN-hANS": "zh-Hans",
+ "cmn-hans-cn": "zh-Hans-CN",
+ "es-419": "es-419",
+ "es-419-u-nu-latn": "es-419-u-nu-latn",
+ "cmn-hans-cn-u-ca-t-ca-x-t-u": "zh-Hans-CN-t-ca-u-ca-x-t-u",
+ "de-gregory-u-ca-gregory": "de-gregory-u-ca-gregory",
+ "sgn-GR": "gss",
+ "ji": "yi",
+ "de-DD": "de-DE",
+ "in": "id",
+ "sr-cyrl-ekavsk": "sr-Cyrl-ekavsk",
+ "en-ca-newfound": "en-CA-newfound",
+ "sl-rozaj-biske-1994": "sl-1994-biske-rozaj",
+ "da-u-attr": "da-u-attr",
+ "da-u-attr-co-search": "da-u-attr-co-search",
+};
+
+// make sure the data above is correct
+Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ var canonicalizedTag = canonicalizedTags[tag];
+ assert(
+ isCanonicalizedStructurallyValidLanguageTag(canonicalizedTag),
+ "Test data \"" + canonicalizedTag + "\" is not canonicalized and structurally valid language tag."
+ );
+});
+
+Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ var canonicalLocales = Intl.getCanonicalLocales(tag);
+ assert.sameValue(canonicalLocales.length, 1);
+ assert.sameValue(canonicalLocales[0], canonicalizedTags[tag]);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-unicode-ext-seq.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-unicode-ext-seq.js
new file mode 100644
index 0000000000..07edfac0e7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/canonicalized-unicode-ext-seq.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Implementations are allowed to canonicalize extension subtag sequences.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
+ ...
+
+ 6.2.3 CanonicalizeLanguageTag (locale)
+ The specifications for extensions to BCP 47 language tags, such as
+ RFC 6067, may include canonicalization rules for the extension subtag
+ sequences they define that go beyond the canonicalization rules of
+ RFC 5646 section 4.5. Implementations are allowed, but not required,
+ to apply these additional rules.
+---*/
+
+var locale = "it-u-nu-latn-ca-gregory";
+
+// RFC 6067: The canonical order of keywords is in US-ASCII order by key.
+var sorted = "it-u-ca-gregory-nu-latn";
+
+var canonicalLocales = Intl.getCanonicalLocales(locale);
+assert.sameValue(canonicalLocales.length, 1);
+
+var canonicalLocale = canonicalLocales[0];
+assert((canonicalLocale === locale) || (canonicalLocale === sorted));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-language-subtag-replacement.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-language-subtag-replacement.js
new file mode 100644
index 0000000000..1401971928
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-language-subtag-replacement.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Assert non-simple language subtag replacements work as expected.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+
+ - Replace aliases in the unicode_language_id and tlang (if any) using the following process:
+ - If the language subtag matches the type attribute of a languageAlias element in
+ Supplemental Data, replace the language subtag with the replacement value.
+ 1. If there are additional subtags in the replacement value, add them to the result,
+ but only if there is no corresponding subtag already in the tag.
+
+includes: [testIntl.js]
+---*/
+
+// CLDR contains language mappings where in addition to the language subtag also
+// the script or region subtag is modified, unless they're already present.
+
+const testData = {
+ // "sh" adds "Latn", unless a script subtag is already present.
+ // <languageAlias type="sh" replacement="sr_Latn" reason="legacy"/>
+ "sh": "sr-Latn",
+ "sh-Cyrl": "sr-Cyrl",
+
+ // "cnr" adds "ME", unless a region subtag is already present.
+ // <languageAlias type="cnr" replacement="sr_ME" reason="legacy"/>
+ "cnr": "sr-ME",
+ "cnr-BA": "sr-BA",
+};
+
+for (let [tag, canonical] of Object.entries(testData)) {
+ // Make sure the test data is correct.
+ assert(
+ isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonicalized and structurally valid language tag."
+ );
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js
new file mode 100644
index 0000000000..f8a316d6f2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js
@@ -0,0 +1,110 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Assert non-simple region subtag replacements work as expected.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+
+ - Replace aliases in the unicode_language_id and tlang (if any) using the following process:
+ - If the region subtag matches the type attribute of a territoryAlias element in
+ Supplemental Data, replace the language subtag with the replacement value, as follows:
+ 1. If there is a single territory in the replacement, use it.
+ 2. If there are multiple territories:
+ 1. Look up the most likely territory for the base language code (and script, if there is one).
+ 2. If that likely territory is in the list, use it.
+ 3. Otherwise, use the first territory in the list.
+
+includes: [testIntl.js]
+---*/
+
+// CLDR contains region mappings where the replacement region depends on the
+// likely subtags from the language and script subtags.
+
+const testData = {
+ // For example, the breakup of the Soviet Union ("SU") means that the region of
+ // the Soviet Union ("SU") is replaced by Russia ("RU"), Armenia ("AM"), or
+ // many others -- depending on the specified (or merely likely) language and
+ // script subtags:
+ //
+ // <territoryAlias type="SU" replacement="RU AM AZ BY EE GE KZ KG LV LT MD TJ TM UA UZ" reason="deprecated"/>
+ // <territoryAlias type="810" replacement="RU AM AZ BY EE GE KZ KG LV LT MD TJ TM UA UZ" reason="overlong"/>
+ "ru-SU": "ru-RU",
+ "ru-810": "ru-RU",
+ "en-SU": "en-RU",
+ "en-810": "en-RU",
+ "und-SU": "und-RU",
+ "und-810": "und-RU",
+ "und-Latn-SU": "und-Latn-RU",
+ "und-Latn-810": "und-Latn-RU",
+
+ // Armenia can be the preferred region when the language is "hy" (Armenian) or
+ // the script is "Armn" (Armenian).
+ //
+ // <likelySubtag from="hy" to="hy_Armn_AM"/>
+ // <likelySubtag from="und_Armn" to="hy_Armn_AM"/>
+ "hy-SU": "hy-AM",
+ "hy-810": "hy-AM",
+ "und-Armn-SU": "und-Armn-AM",
+ "und-Armn-810": "und-Armn-AM",
+
+ // <territoryAlias type="CS" replacement="RS ME" reason="deprecated"/>
+ //
+ // The following likely-subtags entries contain "RS" and "ME":
+ //
+ // <likelySubtag from="sr" to="sr_Cyrl_RS"/>
+ // <likelySubtag from="sr_ME" to="sr_Latn_ME"/>
+ // <likelySubtag from="und_RS" to="sr_Cyrl_RS"/>
+ // <likelySubtag from="und_ME" to="sr_Latn_ME"/>
+ //
+ // In this case there is no language/script combination (without a region
+ // subtag) where "ME" is ever chosen, so the replacement is always "RS".
+ "sr-CS": "sr-RS",
+ "sr-Latn-CS": "sr-Latn-RS",
+ "sr-Cyrl-CS": "sr-Cyrl-RS",
+
+ // The existing region in the source locale identifier is ignored when selecting
+ // the likely replacement region. For example take "az-NT", which is Azerbaijani
+ // spoken in the Neutral Zone. The replacement region for "NT" is either
+ // "SA" (Saudi-Arabia) or "IQ" (Iraq), and there is also a likely subtags entry
+ // for "az-IQ". But when only looking at the language subtag in "az-NT", "az" is
+ // always resolved to "az-Latn-AZ", and because "AZ" is not in the list ["SA",
+ // "IQ"], the final replacement region is the default for "NT", namely "SA".
+ // That means "az-NT" will be canonicalised to "az-SA" and not "az-IQ", even
+ // though the latter may be a more sensible candidate based on the actual usage
+ // of the target locales.
+ //
+ // <territoryAlias type="NT" replacement="SA IQ" reason="deprecated"/>
+ // <likelySubtag from="az_IQ" to="az_Arab_IQ"/>
+ // <likelySubtag from="az" to="az_Latn_AZ"/>
+ "az-NT": "az-SA",
+};
+
+for (let [tag, canonical] of Object.entries(testData)) {
+ // Make sure the test data is correct.
+ assert(
+ isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonicalized and structurally valid language tag."
+ );
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/descriptor.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/descriptor.js
new file mode 100644
index 0000000000..4251540049
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/descriptor.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Intl.getCanonicalLocales property attributes.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every other data property described in clauses 18 through 26 and in
+ Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+ [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, 'getCanonicalLocales', {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/duplicates.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/duplicates.js
new file mode 100644
index 0000000000..a3697544c0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/duplicates.js
@@ -0,0 +1,20 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests the getCanonicalLocales function for duplicate locales scenario.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [compareArray.js]
+---*/
+
+assert.compareArray(Intl.getCanonicalLocales(
+ ['ab-cd', 'ff', 'de-rt', 'ab-Cd']), ['ab-CD', 'ff', 'de-RT']);
+
+var locales = Intl.getCanonicalLocales(['en-US', 'en-US']);
+assert.compareArray(locales, ['en-US'], 'en-US');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/elements-not-reordered.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/elements-not-reordered.js
new file mode 100644
index 0000000000..67a1239d62
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/elements-not-reordered.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Language tags are not reordered.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ vi. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
+ ...
+---*/
+
+var canonicalLocales = Intl.getCanonicalLocales(["zu", "af"]);
+
+assert.sameValue(canonicalLocales.length, 2);
+assert.sameValue(canonicalLocales[0], "zu");
+assert.sameValue(canonicalLocales[1], "af");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/error-cases.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/error-cases.js
new file mode 100644
index 0000000000..b0c4b8a9a8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/error-cases.js
@@ -0,0 +1,48 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests the getCanonicalLocales function for error tags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+features: [Symbol]
+---*/
+
+var rangeErrorCases =
+ [
+ "en-us-",
+ "-en-us",
+ "en-us-en-us",
+ "--",
+ "-",
+ "",
+ "-e-"
+ ];
+
+rangeErrorCases.forEach(function(re) {
+ assert.throws(RangeError, function() {
+ Intl.getCanonicalLocales(re);
+ });
+});
+
+var typeErrorCases =
+ [
+ null,
+ [null],
+ [undefined],
+ [true],
+ [NaN],
+ [2],
+ [Symbol('foo')]
+ ];
+
+typeErrorCases.forEach(function(te) {
+ assert.throws(TypeError, function() {
+ Intl.getCanonicalLocales(te);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/get-locale.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/get-locale.js
new file mode 100644
index 0000000000..99032e02c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/get-locale.js
@@ -0,0 +1,27 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Test Intl.getCanonicalLocales for step 7.c.i.
+info: |
+ 9.2.1 CanonicalizeLocaleList (locales)
+ 7. Repeat, while k < len.
+ c. If kPresent is true, then
+ i. Let kValue be ? Get(O, Pk).
+---*/
+
+var locales = {
+ '0': 'en-US',
+ length: 2
+};
+
+Object.defineProperty(locales, "1", {
+ get: function() { throw new Test262Error() }
+});
+
+assert.throws(Test262Error, function() {
+ Intl.getCanonicalLocales(locales);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/getCanonicalLocales.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/getCanonicalLocales.js
new file mode 100644
index 0000000000..77dc3f786b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/getCanonicalLocales.js
@@ -0,0 +1,26 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Property type and descriptor.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(
+ typeof Intl.getCanonicalLocales,
+ 'function',
+ '`typeof Intl.getCanonicalLocales` is `function`'
+);
+
+verifyProperty(Intl, "getCanonicalLocales", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/grandfathered.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/grandfathered.js
new file mode 100644
index 0000000000..4a26d4d2e0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/grandfathered.js
@@ -0,0 +1,35 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+// Split from intl402/Locale/likely-subtags-grandfathered.js
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Verifies canonicalization of specific tags.
+---*/
+
+
+const regularGrandfathered = [
+ {
+ tag: "art-lojban",
+ canonical: "jbo",
+ },
+ {
+ tag: "zh-guoyu",
+ canonical: "zh",
+ },
+ {
+ tag: "zh-hakka",
+ canonical: "hak",
+ },
+ {
+ tag: "zh-xiang",
+ canonical: "hsn",
+ },
+];
+
+for (const {tag, canonical} of regularGrandfathered) {
+ assert.sameValue(Intl.getCanonicalLocales(tag)[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/has-property.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/has-property.js
new file mode 100644
index 0000000000..2c77014cec
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/has-property.js
@@ -0,0 +1,32 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Test Intl.getCanonicalLocales.name for step 7.b.
+info: |
+ 9.2.1 CanonicalizeLocaleList (locales)
+ 7. Repeat, while k < len.
+ b. Let kPresent be HasProperty(O, Pk).
+features: [Proxy]
+---*/
+
+var locales = {
+ '0': 'en-US',
+ '1': 'pt-BR',
+ length: 2
+};
+
+var p = new Proxy(locales, {
+ has: function(_, prop) {
+ if (prop === '0') {
+ throw new Test262Error();
+ }
+ }
+});
+
+assert.throws(Test262Error, function() {
+ Intl.getCanonicalLocales(p);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/invalid-tags.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/invalid-tags.js
new file mode 100644
index 0000000000..22949e8370
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/invalid-tags.js
@@ -0,0 +1,32 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Throws a RangeError if the language tag is invalid.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ ...
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ iv. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ ...
+includes: [testIntl.js]
+---*/
+
+var invalidLanguageTags = getInvalidLanguageTags();
+for (var i = 0; i < invalidLanguageTags.length; ++i) {
+ var invalidTag = invalidLanguageTags[i];
+ assert.throws(RangeError, function() {
+ Intl.getCanonicalLocales(invalidTag)
+ }, "Language tag: " + invalidTag);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/length.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/length.js
new file mode 100644
index 0000000000..d4bdecd5e0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/length.js
@@ -0,0 +1,21 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Intl.getCanonicalLocales.length.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.getCanonicalLocales, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/locales-is-not-a-string.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/locales-is-not-a-string.js
new file mode 100644
index 0000000000..2aa0565785
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/locales-is-not-a-string.js
@@ -0,0 +1,33 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests for scenario where locales is not a string
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [compareArray.js]
+features: [Symbol]
+---*/
+
+var gCL = Intl.getCanonicalLocales;
+
+function assertArray(l, r) {
+ assert.compareArray(l, r, r);
+}
+
+assertArray(gCL(), []);
+assertArray(gCL(undefined), []);
+assertArray(gCL(false), []);
+assertArray(gCL(true), []);
+assertArray(gCL(Symbol("foo")), []);
+assertArray(gCL(NaN), []);
+assertArray(gCL(1), []);
+
+Number.prototype[0] = "en-US";
+Number.prototype.length = 1;
+assertArray(gCL(NaN), ["en-US"]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/main.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/main.js
new file mode 100644
index 0000000000..6881431838
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/main.js
@@ -0,0 +1,34 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests for existance and behavior of Intl.getCanonicalLocales
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [compareArray.js]
+---*/
+
+var gCL = Intl.getCanonicalLocales;
+
+function assertArray(l, r) {
+ assert.compareArray(l, r, r);
+}
+
+assertArray(gCL(), []);
+
+assertArray(gCL('ab-cd'), ['ab-CD']);
+
+assertArray(gCL(['ab-cd']), ['ab-CD']);
+
+assertArray(gCL(['ab-cd', 'FF']), ['ab-CD', 'ff']);
+
+assertArray(gCL({'a': 0}), []);
+
+assertArray(gCL({}), []);
+
+assertArray(gCL(['th-th-u-nu-thai']), ['th-TH-u-nu-thai']);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/name.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/name.js
new file mode 100644
index 0000000000..c5984baec6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/name.js
@@ -0,0 +1,21 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Intl.getCanonicalLocales.name value and descriptor.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.getCanonicalLocales, "name", {
+ value: "getCanonicalLocales",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/non-iana-canon.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/non-iana-canon.js
new file mode 100644
index 0000000000..5afd34591a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/non-iana-canon.js
@@ -0,0 +1,81 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+// Slip from intl402/Locale/constructor-non-iana-canon.js
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Verifies canonicalization, of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 10. Return CanonicalizeLanguageTag(tag).
+---*/
+
+// Test some language tags where we know that either CLDR or ICU produce
+// different results compared to the canonicalization specified in RFC 5646.
+var testData = [
+ {
+ tag: "mo",
+ canonical: "ro",
+ },
+ {
+ tag: "es-ES-preeuro",
+ },
+ {
+ tag: "uz-UZ-cyrillic",
+ },
+ {
+ tag: "posix",
+ },
+ {
+ tag: "hi-direct",
+ },
+ {
+ tag: "zh-pinyin",
+ },
+ {
+ tag: "zh-stroke",
+ },
+ {
+ tag: "aar-x-private",
+ // "aar" should be canonicalized into "aa" because "aar" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "aa-x-private",
+ },
+ {
+ tag: "heb-x-private",
+ // "heb" should be canonicalized into "he" because "heb" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "he-x-private",
+ },
+ {
+ tag: "de-u-kf",
+ },
+ {
+ tag: "ces",
+ // "ces" should be canonicalized into "cs" because "ces" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "cs",
+ },
+ {
+ tag: "hy-arevela",
+ canonical: "hy",
+ },
+ {
+ tag: "hy-arevmda",
+ canonical: "hyw",
+ },
+];
+
+for (const {tag, canonical = tag} of testData) {
+ assert.sameValue(
+ Intl.getCanonicalLocales(tag)[0],
+ canonical,
+ 'The value of Intl.getCanonicalLocales(tag)[0] equals the value of `canonical`'
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-arg-length.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-arg-length.js
new file mode 100644
index 0000000000..3d781c0301
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-arg-length.js
@@ -0,0 +1,103 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Test Intl.getCanonicalLocales for step 5.
+info: |
+ 9.2.1 CanonicalizeLocaleList (locales)
+ 5. Let len be ? ToLength(? Get(O, "length")).
+includes: [compareArray.js]
+features: [Symbol]
+---*/
+
+var locales = {
+ '0': 'en-US',
+};
+
+Object.defineProperty(locales, "length", {
+ get: function() { throw new Test262Error() }
+});
+
+assert.throws(Test262Error, function() {
+ Intl.getCanonicalLocales(locales);
+}, "should throw if locales.length throws");
+
+var locales = {
+ '0': 'en-US',
+ '1': 'pt-BR',
+};
+
+Object.defineProperty(locales, "length", {
+ get: function() { return "1" }
+});
+
+assert.compareArray(
+ Intl.getCanonicalLocales(locales),
+ ['en-US'],
+ "should return one element if locales.length is '1'"
+);
+
+var locales = {
+ '0': 'en-US',
+ '1': 'pt-BR',
+};
+
+Object.defineProperty(locales, "length", {
+ get: function() { return 1.3 }
+});
+
+assert.compareArray(
+ Intl.getCanonicalLocales(locales),
+ ['en-US'],
+ "should return one element if locales.length is 1.3"
+);
+
+var locales = {
+ '0': 'en-US',
+ '1': 'pt-BR',
+};
+
+Object.defineProperty(locales, "length", {
+ get: function() { return Symbol("1.8") }
+});
+
+assert.throws(TypeError, function() {
+ Intl.getCanonicalLocales(locales);
+}, "should throw if locales.length is a Symbol");
+
+var locales = {
+ '0': 'en-US',
+ '1': 'pt-BR',
+};
+
+Object.defineProperty(locales, "length", {
+ get: function() { return -Infinity }
+});
+
+assert.compareArray(
+ Intl.getCanonicalLocales(locales),
+ [],
+ "should return empty array if locales.length is -Infinity"
+);
+
+var locales = {
+ length: -Math.pow(2, 32) + 1
+};
+
+Object.defineProperty(locales, "0", {
+ get: function() { throw new Error("must not be gotten!"); }
+})
+
+assert.compareArray(
+ Intl.getCanonicalLocales(locales),
+ [],
+ "should return empty array if locales.length is a negative value"
+);
+
+var count = 0;
+var locs = { get length() { if (count++ > 0) throw 42; return 0; } };
+var locales = Intl.getCanonicalLocales(locs); // shouldn't throw 42
+assert.sameValue(locales.length, 0);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-push.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-push.js
new file mode 100644
index 0000000000..d8c8a794ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/overriden-push.js
@@ -0,0 +1,21 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests the getCanonicalLocales function for overridden Array.push().
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [compareArray.js]
+---*/
+
+Array.prototype.push = function() { throw 42; };
+
+// must not throw 42, might if push is used
+var arr = Intl.getCanonicalLocales(["en-US"]);
+
+assert.compareArray(arr, ["en-US"]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-grandfathered.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-grandfathered.js
new file mode 100644
index 0000000000..51a536bbfc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-grandfathered.js
@@ -0,0 +1,98 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Call Intl.getCanonicalLocales function with grandfathered language tags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
+ ...
+
+ 6.2.3 CanonicalizeLanguageTag ( locale )
+ The CanonicalizeLanguageTag abstract operation returns the canonical and case-regularized form
+ of the locale argument (which must be a String value that is a structurally valid Unicode
+ BCP 47 Locale Identifier as verified by the IsStructurallyValidLanguageTag abstract operation).
+ A conforming implementation shall take the steps specified in the “BCP 47 Language Tag to
+ Unicode BCP 47 Locale Identifier” algorithm, from Unicode Technical Standard #35 LDML
+ § 3.3.1 BCP 47 Language Tag Conversion.
+
+includes: [testIntl.js]
+---*/
+
+// Generated from http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+// File-Date: 2017-08-15
+
+var irregularGrandfathered = [
+ "en-gb-oed",
+ "i-ami",
+ "i-bnn",
+ "i-default",
+ "i-enochian",
+ "i-hak",
+ "i-klingon",
+ "i-lux",
+ "i-mingo",
+ "i-navajo",
+ "i-pwn",
+ "i-tao",
+ "i-tay",
+ "i-tsu",
+ "sgn-be-fr",
+ "sgn-be-nl",
+ "sgn-ch-de",
+];
+
+var regularGrandfatheredNonUTS35 = [
+ "no-bok",
+ "no-nyn",
+ "zh-min",
+ "zh-min-nan",
+];
+
+var regularGrandfatheredUTS35 = {
+ "art-lojban": "jbo",
+ "cel-gaulish": "xtg",
+ "zh-guoyu": "zh",
+ "zh-hakka": "hak",
+ "zh-xiang": "hsn",
+};
+
+// make sure the data above is correct
+irregularGrandfathered.forEach(function (tag) {
+ assert.sameValue(
+ isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "Test data \"" + tag + "\" is not a structurally valid language tag."
+ );
+});
+regularGrandfatheredNonUTS35.forEach(function (tag) {
+ assert.sameValue(
+ isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "Test data \"" + tag + "\" is not a structurally valid language tag."
+ );
+});
+Object.getOwnPropertyNames(regularGrandfatheredUTS35).forEach(function (tag) {
+ var canonicalizedTag = regularGrandfatheredUTS35[tag];
+ assert(
+ isCanonicalizedStructurallyValidLanguageTag(canonicalizedTag),
+ "Test data \"" + canonicalizedTag + "\" is a canonicalized and structurally valid language tag."
+ );
+});
+
+Object.getOwnPropertyNames(regularGrandfatheredUTS35).forEach(function (tag) {
+ var canonicalLocales = Intl.getCanonicalLocales(tag);
+ assert.sameValue(canonicalLocales.length, 1);
+ assert.sameValue(canonicalLocales[0], regularGrandfatheredUTS35[tag]);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-variant.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-variant.js
new file mode 100644
index 0000000000..e1d8f028cc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/preferred-variant.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Call Intl.getCanonicalLocales function with grandfathered language tags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
+ ...
+
+ 6.2.3 CanonicalizeLanguageTag ( locale )
+ The CanonicalizeLanguageTag abstract operation returns the canonical and case-regularized
+ form of the locale argument (which must be a String value that is a structurally valid
+ BCP 47 language tag as verified by the IsStructurallyValidLanguageTag abstract operation).
+ A conforming implementation shall take the steps specified in RFC 5646 section 4.5, or
+ successor, to bring the language tag into canonical form, and to regularize the case of
+ the subtags. Furthermore, a conforming implementation shall not take the steps to bring
+ a language tag into "extlang form", nor shall it reorder variant subtags.
+
+ The specifications for extensions to BCP 47 language tags, such as RFC 6067, may include
+ canonicalization rules for the extension subtag sequences they define that go beyond the
+ canonicalization rules of RFC 5646 section 4.5. Implementations are allowed, but not
+ required, to apply these additional rules.
+
+includes: [testIntl.js]
+---*/
+
+// https://github.com/unicode-org/cldr/blame/master/common/supplemental/supplementalMetadata.xml#L531
+// http://unicode.org/reports/tr35/#LocaleId_Canonicalization
+var canonicalizedTags = {
+ "ja-latn-hepburn-heploc": "ja-Latn-alalc97",
+};
+
+// make sure the data above is correct
+Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ var canonicalizedTag = canonicalizedTags[tag];
+ assert(
+ isCanonicalizedStructurallyValidLanguageTag(canonicalizedTag),
+ "Test data \"" + canonicalizedTag + "\" is not canonicalized and structurally valid language tag."
+ );
+});
+
+Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ var canonicalLocales = Intl.getCanonicalLocales(tag);
+ assert.sameValue(canonicalLocales.length, 1);
+ assert.sameValue(canonicalLocales[0], canonicalizedTags[tag]);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-an-array.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-an-array.js
new file mode 100644
index 0000000000..193393484d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-an-array.js
@@ -0,0 +1,24 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests that the value returned by getCanonicalLocales is an Array.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+---*/
+
+var locales = ['en-US'];
+var result = Intl.getCanonicalLocales(['en-US']);
+
+assert.sameValue(Object.getPrototypeOf(result), Array.prototype, 'prototype is Array.prototype');
+assert.sameValue(result.constructor, Array);
+
+assert.notSameValue(result, locales, "result is a new array instance");
+assert.sameValue(result.length, 1, "result.length");
+assert(result.hasOwnProperty("0"), "result an own property `0`");
+assert.sameValue(result[0], "en-US", "result[0]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-mutable.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-mutable.js
new file mode 100644
index 0000000000..af9fd8c8c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/returned-object-is-mutable.js
@@ -0,0 +1,49 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Tests that the value returned by getCanonicalLocales is a mutable array.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [propertyHelper.js]
+---*/
+
+var locales = ['en-US', 'fr'];
+var result = Intl.getCanonicalLocales(locales);
+
+verifyProperty(result, 0, {
+ value: 'en-US',
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+result = Intl.getCanonicalLocales(locales);
+
+verifyProperty(result, 1, {
+ value: 'fr',
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+result = Intl.getCanonicalLocales(locales);
+
+verifyProperty(result, "length", {
+ value: 2,
+ writable: true,
+ enumerable: false,
+ configurable: false,
+});
+
+result.length = 42;
+assert.sameValue(result.length, 42);
+
+assert.throws(RangeError, function() {
+ result.length = "Leo";
+}, "a non-numeric value can't be set to result.length");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/shell.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/to-string.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/to-string.js
new file mode 100644
index 0000000000..464a4cb03b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/to-string.js
@@ -0,0 +1,22 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Test Intl.getCanonicalLocales.name for step 7.c.iii
+info: |
+ 9.2.1 CanonicalizeLocaleList (locales)
+ 7. Repeat, while k < len.
+ c. If kPresent is true, then
+ iii. Let tag be ? ToString(kValue).
+includes: [compareArray.js]
+---*/
+
+var locales = {
+ '0': { toString: function() { locales[1] = 'pt-BR'; return 'en-US'; }},
+ length: 2
+};
+
+assert.compareArray(Intl.getCanonicalLocales(locales), [ "en-US", "pt-BR" ]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-canonical.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-canonical.js
new file mode 100644
index 0000000000..c6717a364b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-canonical.js
@@ -0,0 +1,56 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test canonicalisation within transformed extension subtags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+includes: [testIntl.js]
+---*/
+
+const testData = {
+ // Variant subtags are alphabetically ordered.
+ "sl-t-sl-rozaj-biske-1994": "sl-t-sl-1994-biske-rozaj",
+
+ // tfield subtags are alphabetically ordered.
+ // (Also tests subtag case normalisation.)
+ "DE-T-M0-DIN-K0-QWERTZ": "de-t-k0-qwertz-m0-din",
+
+ // "true" tvalue subtags aren't removed.
+ // (UTS 35 version 36, §3.2.1 claims otherwise, but tkey must be followed by
+ // tvalue, so that's likely a spec bug in UTS 35.)
+ "en-t-m0-true": "en-t-m0-true",
+
+ // tlang subtags are canonicalised.
+ "en-t-iw": "en-t-he",
+
+ // Deprecated tvalue subtags are replaced by their preferred value.
+ "und-Latn-t-und-hani-m0-names": "und-Latn-t-und-hani-m0-prprname",
+};
+
+for (let [tag, canonical] of Object.entries(testData)) {
+ // Make sure the test data is correct.
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-invalid.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-invalid.js
new file mode 100644
index 0000000000..2a278ce2b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-invalid.js
@@ -0,0 +1,80 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ A RangeError is thrown when a language tag includes an invalid transformed extension subtag.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ ...
+
+includes: [testIntl.js]
+---*/
+
+const invalid = [
+ // empty
+ "en-t",
+ "en-t-a",
+ "en-t-x",
+ "en-t-0",
+
+ // incomplete
+ "en-t-",
+ "en-t-en-",
+ "en-t-0x-",
+
+ // tlang: unicode_language_subtag must be 2-3 or 5-8 characters and mustn't
+ // contain extlang subtags.
+ "en-t-root",
+ "en-t-abcdefghi",
+ "en-t-ar-aao",
+
+ // tlang: unicode_script_subtag must be 4 alphabetical characters, can't
+ // be repeated.
+ "en-t-en-lat0",
+ "en-t-en-latn-latn",
+
+ // tlang: unicode_region_subtag must either be 2 alpha characters or a three
+ // digit code.
+ "en-t-en-0",
+ "en-t-en-00",
+ "en-t-en-0x",
+ "en-t-en-x0",
+ "en-t-en-latn-0",
+ "en-t-en-latn-00",
+ "en-t-en-latn-xyz",
+
+ // tlang: unicode_variant_subtag is either 5-8 alphanum characters or 4
+ // characters starting with a digit.
+ "en-t-en-abcdefghi",
+ "en-t-en-latn-gb-ab",
+ "en-t-en-latn-gb-abc",
+ "en-t-en-latn-gb-abcd",
+ "en-t-en-latn-gb-abcdefghi",
+
+ // tkey must be followed by tvalue.
+ "en-t-d0",
+ "en-t-d0-m0",
+ "en-t-d0-x-private",
+];
+
+for (let tag of invalid) {
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a structurally valid language tag.");
+
+ assert.throws(RangeError, () => Intl.getCanonicalLocales(tag), `${tag}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-valid.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-valid.js
new file mode 100644
index 0000000000..6668b94ad8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/transformed-ext-valid.js
@@ -0,0 +1,80 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ No RangeError is thrown when a language tag includes a valid transformed extension subtag.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+includes: [testIntl.js]
+---*/
+
+const valid = [
+ // tlang with unicode_language_subtag.
+ "en-t-en",
+
+ // tlang with unicode_script_subtag.
+ "en-t-en-latn",
+
+ // tlang with unicode_region_subtag.
+ "en-t-en-ca",
+
+ // tlang with unicode_script_subtag and unicode_region_subtag.
+ "en-t-en-latn-ca",
+
+ // tlang with unicode_variant_subtag.
+ "en-t-en-emodeng",
+
+ // tlang with unicode_script_subtag and unicode_variant_subtag.
+ "en-t-en-latn-emodeng",
+
+ // tlang with unicode_script_subtag and unicode_variant_subtag.
+ "en-t-en-ca-emodeng",
+
+ // tlang with unicode_script_subtag, unicode_region_subtag, and unicode_variant_subtag.
+ "en-t-en-latn-ca-emodeng",
+
+ // No tlang. (Must contain at least one tfield.)
+ "en-t-d0-ascii",
+];
+
+const extraFields = [
+ // No extra tfield
+ "",
+
+ // tfield with a tvalue consisting of a single subtag.
+ "-i0-handwrit",
+
+ // tfield with a tvalue consisting of two subtags.
+ "-s0-accents-publish",
+];
+
+for (let tag of valid) {
+ for (let extra of extraFields) {
+ let actualTag = tag + extra;
+
+ // Make sure the test data is correct.
+ assert(isCanonicalizedStructurallyValidLanguageTag(actualTag),
+ "\"" + actualTag + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(actualTag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], actualTag);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-calendar.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-calendar.js
new file mode 100644
index 0000000000..736b8e7013
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-calendar.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "ca" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+includes: [testIntl.js]
+---*/
+
+// <key name="ca" [...] alias="calendar">
+const testData = {
+ // <type name="ethioaa" [...] alias="ethiopic-amete-alem"/>
+ "ethiopic-amete-alem": "ethioaa",
+
+ // <type name="islamic-civil" [...] />
+ // <type name="islamicc" [...] deprecated="true" preferred="islamic-civil" alias="islamic-civil"/>
+ //
+ // "name" and "alias" for "islamic-civil" don't quite match of what's spec'ed in UTS 35, §3.2.1.
+ // Specifically following §3.2.1 to the letter means "islamicc" is the canonical value whereas
+ // "islamic-civil" is an alias value. Assume the definitions in
+ // https://unicode.org/reports/tr35/#Unicode_Locale_Extension_Data_Files overrule UTS 35, §3.2.1.
+ "islamicc": "islamic-civil",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ let tag = "und-u-ca-" + alias;
+ let canonical = "und-u-ca-" + name;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-col-strength.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-col-strength.js
new file mode 100644
index 0000000000..48c033d7e1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-col-strength.js
@@ -0,0 +1,67 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "ks" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+includes: [testIntl.js]
+---*/
+
+// <key name="ks" [...] alias="colStrength">/
+const testData = {
+ // <type name="level1" [...] alias="primary"/>
+ "primary": "level1",
+
+ // "secondary" doesn't match |uvalue|, so we can skip it.
+ // <type name="level2" [...] alias="secondary"/>
+ // "secondary": "level2",
+
+ // <type name="level3" [...] alias="tertiary"/>
+ "tertiary": "level3",
+
+ // Neither "quaternary" nor "quarternary" match |uvalue|, so we can skip them.
+ // <type name="level4" [...] alias="quaternary quarternary"/>
+ // "quaternary": "level4",
+ // "quarternary": "level4",
+
+ // "identical" doesn't match |uvalue|, so we can skip it.
+ // <type name="identic" [...] alias="identical"/>
+ // "identical": "identic",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ let tag = "und-u-ks-" + alias;
+ let canonical = "und-u-ks-" + name;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-measurement-system.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-measurement-system.js
new file mode 100644
index 0000000000..28c587155b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-measurement-system.js
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "ms" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+includes: [testIntl.js]
+---*/
+
+// <key name="ms" [...] alias="measure" since="28">
+const testData = {
+ // <type name="uksystem" [...] alias="imperial" since="28" />
+ "imperial": "uksystem",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ let tag = "und-u-ms-" + alias;
+ let canonical = "und-u-ms-" + name;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-region.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-region.js
new file mode 100644
index 0000000000..34e1e95082
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-region.js
@@ -0,0 +1,69 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "rg" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+
+ Replace aliases in special key values:
+ If there is an 'sd' or 'rg' key, replace any subdivision alias in its value in the same way,
+ using subdivisionAlias data.
+includes: [testIntl.js]
+---*/
+
+const testData = {
+ // <subdivisionAlias type="no23" replacement="no50" reason="deprecated"/>
+ "no23": "no50",
+
+ // <subdivisionAlias type="cn11" replacement="cnbj" reason="deprecated"/>
+ "cn11": "cnbj",
+
+ // <subdivisionAlias type="cz10a" replacement="cz110" reason="deprecated"/>
+ "cz10a": "cz110",
+
+ // <subdivisionAlias type="fra" replacement="frges" reason="deprecated"/>
+ "fra": "frges",
+
+ // <subdivisionAlias type="frg" replacement="frges" reason="deprecated"/>
+ "frg": "frges",
+
+ // <subdivisionAlias type="lud" replacement="lucl ludi lurd luvd luwi" reason="deprecated"/>
+ "lud": "lucl",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ let tag = "und-u-rg-" + alias;
+ let canonical = "und-u-rg-" + name;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-subdivision.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-subdivision.js
new file mode 100644
index 0000000000..d7f9f6ff4f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-subdivision.js
@@ -0,0 +1,74 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "sd" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+
+ Replace aliases in special key values:
+ If there is an 'sd' or 'rg' key, replace any subdivision alias in its value in the same way,
+ using subdivisionAlias data.
+includes: [testIntl.js]
+---*/
+
+const testData = {
+ // <subdivisionAlias type="no23" replacement="no50" reason="deprecated"/>
+ "no23": "no50",
+
+ // <subdivisionAlias type="cn11" replacement="cnbj" reason="deprecated"/>
+ "cn11": "cnbj",
+
+ // <subdivisionAlias type="cz10a" replacement="cz110" reason="deprecated"/>
+ "cz10a": "cz110",
+
+ // <subdivisionAlias type="fra" replacement="frges" reason="deprecated"/>
+ "fra": "frges",
+
+ // <subdivisionAlias type="frg" replacement="frges" reason="deprecated"/>
+ "frg": "frges",
+
+ // <subdivisionAlias type="lud" replacement="lucl ludi lurd luvd luwi" reason="deprecated"/>
+ "lud": "lucl",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ // Subdivision codes should always have a matching region subtag. This
+ // shouldn't actually matter for canonicalisation, but let's not push our
+ // luck and instead keep the language tag 'valid' per UTS 35, §3.6.5.
+ let region = name.substring(0, 2).toUpperCase();
+
+ let tag = `und-${region}-u-sd-${alias}`;
+ let canonical = `und-${region}-u-sd-${name}`;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-timezone.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-timezone.js
new file mode 100644
index 0000000000..8a6a780c83
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-timezone.js
@@ -0,0 +1,74 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtag canonicalisation for the "tz" extension key.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+includes: [testIntl.js]
+---*/
+
+// <key name="tz" [...] alias="timezone">
+const testData = {
+ // Similar to the "ca" extension key, assume "preferred" holds the canonical
+ // value and "name" the alias value.
+
+ // <type name="cnckg" [...] deprecated="true" preferred="cnsha"/>
+ "cnckg": "cnsha",
+
+ // NB: "Eire" matches the |uvalue| production.
+ // <type name="iedub" [...] alias="Europe/Dublin Eire"/>
+ "eire": "iedub",
+
+ // NB: "EST" matches the |uvalue| production.
+ // <type name="utcw05" [...] alias="Etc/GMT+5 EST"/>
+ "est": "utcw05",
+
+ // NB: "GMT0" matches the |uvalue| production.
+ // <type name="gmt" [...] alias="Etc/GMT Etc/GMT+0 Etc/GMT-0 Etc/GMT0 Etc/Greenwich GMT GMT+0 GMT-0 GMT0 Greenwich"/>
+ "gmt0": "gmt",
+
+ // NB: "UCT" matches the |uvalue| production.
+ // <type name="utc" [...] alias="Etc/UTC Etc/UCT Etc/Universal Etc/Zulu UCT UTC Universal Zulu"/>
+ "uct": "utc",
+
+ // NB: "Zulu" matches the |uvalue| production.
+ // <type name="utc" [...] alias="Etc/UTC Etc/UCT Etc/Universal Etc/Zulu UCT UTC Universal Zulu"/>
+ "zulu": "utc",
+};
+
+for (let [alias, name] of Object.entries(testData)) {
+ let tag = "und-u-tz-" + alias;
+ let canonical = "und-u-tz-" + name;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js
new file mode 100644
index 0000000000..7ef23a3586
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js
@@ -0,0 +1,88 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ "kb", "kc", "kh", "kk", and "kn" Unicode extension keys canonicalise "yes" to "true".
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Use the bcp47 data to replace keys, types, tfields, and tvalues by their canonical forms.
+ See Section 3.6.4 U Extension Data Files) and Section 3.7.1 T Extension Data Files. The
+ aliases are in the alias attribute value, while the canonical is in the name attribute value.
+
+ UTS 35, §3.2.1 Canonical Unicode Locale Identifiers
+ Any type or tfield value "true" is removed.
+includes: [testIntl.js]
+---*/
+
+const unicodeKeys = [
+ // <key name="kb" [...] alias="colBackwards">
+ // <type name="true" [...] alias="yes"/>
+ "kb",
+
+ // <key name="kc" [...] alias="colCaseLevel">
+ // <type name="true" [...] alias="yes"/>
+ "kc",
+
+ // <key name="kh" [...] alias="colBackwards">
+ // <type name="true" [...] alias="yes"/>
+ "kh",
+
+ // <key name="kh" [...] alias="colHiraganaQuaternary">
+ // <type name="true" [...] alias="yes"/>
+ "kk",
+
+ // <key name="kn" [...] alias="colNumeric">
+ // <type name="true" [...] alias="yes"/>
+ "kn",
+];
+
+for (let key of unicodeKeys) {
+ let tag = `und-u-${key}-yes`;
+ let canonical = `und-u-${key}`;
+
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(tag), false,
+ "\"" + tag + "\" isn't a canonical language tag.");
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonical),
+ "\"" + canonical + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], canonical);
+}
+
+// Test some other Unicode extension keys which don't contain an alias entry to
+// canonicalise "yes" to "true".
+const otherUnicodeKeys = [
+ "ka", "kf", "kr", "ks", "kv",
+];
+
+for (let key of otherUnicodeKeys) {
+ let tag = `und-u-${key}-yes`;
+
+ // Make sure the test data is correct.
+ assert(isCanonicalizedStructurallyValidLanguageTag(tag),
+ "\"" + tag + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(tag);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], tag);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-key-with-digit.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-key-with-digit.js
new file mode 100644
index 0000000000..9271e01f7a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/unicode-ext-key-with-digit.js
@@ -0,0 +1,56 @@
+// Copyright (C) 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: >
+ Test Unicode extension subtags where the ukey subtag contains a digit.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+
+ 9.2.1 CanonicalizeLocaleList (locales)
+ ...
+ 7. Repeat, while k < len
+ ...
+ c. If kPresent is true, then
+ ...
+ v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
+ ...
+
+includes: [testIntl.js]
+---*/
+
+// Unicode locale extension sequences don't allow keys with a digit as their
+// second character.
+const invalidCases = [
+ "en-u-c0",
+ "en-u-00",
+];
+
+// The first character is allowed to be a digit.
+const validCases = [
+ "en-u-0c",
+];
+
+for (let invalid of invalidCases) {
+ // Make sure the test data is correct.
+ assert.sameValue(isCanonicalizedStructurallyValidLanguageTag(invalid), false,
+ "\"" + invalid + "\" isn't a structurally valid language tag.");
+
+ assert.throws(RangeError, () => Intl.getCanonicalLocales(invalid));
+}
+
+for (let valid of validCases) {
+ // Make sure the test data is correct.
+ assert(isCanonicalizedStructurallyValidLanguageTag(valid),
+ "\"" + valid + "\" is a canonical and structurally valid language tag.");
+
+ let result = Intl.getCanonicalLocales(valid);
+ assert.sameValue(result.length, 1);
+ assert.sameValue(result[0], valid);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/getCanonicalLocales/weird-cases.js b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/weird-cases.js
new file mode 100644
index 0000000000..e7b680b570
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/getCanonicalLocales/weird-cases.js
@@ -0,0 +1,26 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.getcanonicallocales
+description: Tests the getCanonicalLocales function for weird tags.
+info: |
+ 8.2.1 Intl.getCanonicalLocales (locales)
+ 1. Let ll be ? CanonicalizeLocaleList(locales).
+ 2. Return CreateArrayFromList(ll).
+includes: [compareArray.js]
+---*/
+
+var weirdCases =
+ [
+ "en-x-u-foo",
+ "en-a-bar-x-u-foo",
+ "en-x-u-foo-a-bar",
+ "en-a-bar-u-baz-x-u-foo",
+ ];
+
+weirdCases.forEach(function (weird) {
+ assert.compareArray(Intl.getCanonicalLocales(weird), [weird]);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/shell.js b/js/src/tests/test262/intl402/Intl/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/browser.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/builtin.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/builtin.js
new file mode 100644
index 0000000000..8689ef54af
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/builtin.js
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Intl.supportedValuesOf is a built-in function object..
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 18 ECMAScript Standard Built-in Objects:
+ Unless specified otherwise, a built-in object that is callable as a function
+ is a built-in function object with the characteristics described in 10.3.
+ Unless specified otherwise, the [[Extensible]] internal slot of a built-in
+ object initially has the value true.
+
+ Unless otherwise specified every built-in function and every built-in
+ constructor has the Function prototype object, which is the initial value
+ of the expression Function.prototype (20.2.3), as the value of its
+ [[Prototype]] internal slot.
+
+ Built-in function objects that are not identified as constructors do not
+ implement the [[Construct]] internal method unless otherwise specified in
+ the description of a particular function.
+includes: [isConstructor.js]
+features: [Intl-enumeration, Reflect.construct]
+---*/
+
+assert.sameValue(typeof Intl.supportedValuesOf, "function",
+ "Intl.supportedValuesOf is a function");
+
+assert(!Object.prototype.hasOwnProperty.call(Intl.supportedValuesOf, "prototype"),
+ "Intl.supportedValuesOf doesn't have an own 'prototype' property");
+
+assert(Object.isExtensible(Intl.supportedValuesOf),
+ "Built-in objects must be extensible");
+
+assert.sameValue(Object.getPrototypeOf(Intl.supportedValuesOf), Function.prototype,
+ "[[Prototype]] of Intl.supportedValuesOf is Function.prototype");
+
+assert(!isConstructor(Intl.supportedValuesOf),
+ "Intl.supportedValuesOf not a constructor function");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DateTimeFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DateTimeFormat.js
new file mode 100644
index 0000000000..9cbaa39c06
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DateTimeFormat.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "calendar" values can be used with DateTimeFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ 2. If key is "calendar", then
+ a. Let list be ! AvailableCalendars( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCalendars ( )
+ The AvailableCalendars abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique calendar types identifying the
+ calendars for which the implementation provides the functionality of
+ Intl.DateTimeFormat objects. The list must include "gregory".
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const calendars = Intl.supportedValuesOf("calendar");
+
+for (let calendar of calendars) {
+ let obj = new Intl.DateTimeFormat("en", {calendar});
+ assert.sameValue(obj.resolvedOptions().calendar, calendar,
+ `${calendar} is supported by DateTimeFormat`);
+}
+
+for (let calendar of allCalendars()) {
+ let obj = new Intl.DateTimeFormat("en", {calendar});
+ if (obj.resolvedOptions().calendar === calendar) {
+ assert(calendars.includes(calendar),
+ `${calendar} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!calendars.includes(calendar),
+ `${calendar} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DisplayNames.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DisplayNames.js
new file mode 100644
index 0000000000..b1c34fd51c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars-accepted-by-DisplayNames.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "calendar" values can be used with DisplayNames.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ 2. If key is "calendar", then
+ a. Let list be ! AvailableCalendars( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCalendars ( )
+ The AvailableCalendars abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique calendar types identifying the
+ calendars for which the implementation provides the functionality of
+ Intl.DateTimeFormat objects. The list must include "gregory".
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Intl.DisplayNames-v2, Array.prototype.includes]
+---*/
+
+const calendars = Intl.supportedValuesOf("calendar");
+
+const obj = new Intl.DisplayNames("en", {type: "calendar", fallback: "none"});
+
+for (let calendar of calendars) {
+ assert.sameValue(typeof obj.of(calendar), "string",
+ `${calendar} is supported by DisplayNames`);
+}
+
+for (let calendar of allCalendars()) {
+ if (typeof obj.of(calendar) === "string") {
+ assert(calendars.includes(calendar),
+ `${calendar} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!calendars.includes(calendar),
+ `${calendar} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars.js
new file mode 100644
index 0000000000..4de1a5e689
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/calendars.js
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "calendar" values are sorted, unique, and match the type production.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ 2. If key is "calendar", then
+ a. Let list be ! AvailableCalendars( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCalendars ( )
+ The AvailableCalendars abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique calendar types identifying the
+ calendars for which the implementation provides the functionality of
+ Intl.DateTimeFormat objects. The list must include "gregory".
+includes: [compareArray.js]
+features: [Intl-enumeration, Intl.Locale, Array.prototype.includes]
+---*/
+
+const calendars = Intl.supportedValuesOf("calendar");
+
+assert(Array.isArray(calendars), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(calendars), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherCalendars = Intl.supportedValuesOf("calendar");
+assert.notSameValue(otherCalendars, calendars,
+ "Returns a new array object for each call.");
+
+assert.compareArray(calendars, otherCalendars.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(calendars).size, calendars.length,
+ "The array doesn't contain duplicates.");
+
+// https://unicode.org/reports/tr35/tr35.html#Unicode_locale_identifier
+const typeRE = /^[a-z0-9]{3,8}(-[a-z0-9]{3,8})*$/;
+for (let calendar of calendars) {
+ assert(typeRE.test(calendar), `${calendar} matches the 'type' production`);
+}
+
+for (let calendar of calendars) {
+ assert.sameValue(new Intl.Locale("und", {calendar}).calendar, calendar,
+ `${calendar} is canonicalised`);
+}
+
+assert(calendars.includes("gregory"), "Includes the Gregorian calendar.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/coerced-to-string.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/coerced-to-string.js
new file mode 100644
index 0000000000..b5354265b9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/coerced-to-string.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Input key is coerced with ToString.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ 2. If key is "calendar", then
+ a. Let list be ! AvailableCalendars( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+includes: [compareArray.js]
+features: [Intl-enumeration]
+---*/
+
+const calendars = Intl.supportedValuesOf("calendar");
+
+// ToString on a String object.
+assert.compareArray(Intl.supportedValuesOf(new String("calendar")), calendars);
+
+// ToString on a plain object.
+let obj = {
+ toString() {
+ return "calendar";
+ }
+};
+assert.compareArray(Intl.supportedValuesOf(obj), calendars);
+
+// ToString() of a symbol throws a TypeError.
+assert.throws(TypeError, function() {
+ Intl.supportedValuesOf(Symbol());
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations-accepted-by-Collator.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations-accepted-by-Collator.js
new file mode 100644
index 0000000000..ee4413581b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations-accepted-by-Collator.js
@@ -0,0 +1,83 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "collation" values can be used with Collator.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 3. Else if key is "collation", then
+ a. Let list be ! AvailableCollations( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCollations ( )
+ The AvailableCollations abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique collation types identifying the
+ collations for which the implementation provides the functionality of
+ Intl.Collator objects.
+includes: [testIntl.js]
+locale: [en, ar, de, es, ko, ln, si, sv, zh]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const collations = Intl.supportedValuesOf("collation");
+
+// Not all locales support all possible collations, so test the minimal set to
+// cover all supported collations.
+//
+// The list of all collations can be derived from
+// <https://github.com/unicode-org/cldr/blob/master/common/bcp47/collation.xml>.
+//
+// Note: "standard" and "search" are explicitly disallowed by Intl.Collator.
+const locales = [
+ "en", // ducet, emoji, eor
+ "ar", // compat
+ "de", // phonebk
+ "es", // trad
+ "hi", // direct
+ "ko", // searchjl
+ "ln", // phonetic
+ "si", // dict
+ "sv", // reformed
+ "zh", // big5han, gb2312, pinyin, stroke, unihan, zhuyin
+];
+
+for (let collation of collations) {
+ let supported = false;
+ for (let locale of locales) {
+ let obj = new Intl.Collator(locale, {collation});
+ if (obj.resolvedOptions().collation === collation) {
+ supported = true;
+ break;
+ }
+ }
+
+ assert(supported, `${collation} is supported by Collator`);
+}
+
+for (let collation of allCollations()) {
+ let supported = false;
+ for (let locale of locales) {
+ let obj = new Intl.Collator(locale, {collation});
+ if (obj.resolvedOptions().collation === collation) {
+ supported = true;
+ break;
+ }
+ }
+
+ if (supported) {
+ assert(collations.includes(collation),
+ `${collation} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!collations.includes(collation),
+ `${collation} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations.js
new file mode 100644
index 0000000000..372f84c7bf
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/collations.js
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "collation" values are sorted, unique, and match the type production.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 3. Else if key is "collation", then
+ a. Let list be ! AvailableCollations( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCollations ( )
+ The AvailableCollations abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique collation types identifying the
+ collations for which the implementation provides the functionality of
+ Intl.Collator objects.
+includes: [compareArray.js]
+features: [Intl-enumeration, Intl.Locale, Array.prototype.includes]
+---*/
+
+const collations = Intl.supportedValuesOf("collation");
+
+assert(Array.isArray(collations), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(collations), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherCollations = Intl.supportedValuesOf("collation");
+assert.notSameValue(otherCollations, collations,
+ "Returns a new array object for each call.");
+
+assert.compareArray(collations, otherCollations.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(collations).size, collations.length,
+ "The array doesn't contain duplicates.");
+
+// https://unicode.org/reports/tr35/tr35.html#Unicode_locale_identifier
+const typeRE = /^[a-z0-9]{3,8}(-[a-z0-9]{3,8})*$/;
+for (let collation of collations) {
+ assert(typeRE.test(collation), `${collation} matches the 'type' production`);
+}
+
+for (let collation of collations) {
+ assert.sameValue(new Intl.Locale("und", {collation}).collation, collation,
+ `${collation} is canonicalised`);
+}
+
+assert(!collations.includes("standard"), "Mustn't include the 'standard' collation type.");
+assert(!collations.includes("search"), "Mustn't include the 'search' collation type.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js
new file mode 100644
index 0000000000..cccc756b9f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "currency" values can be used with DisplayNames.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 4. Else if key is "currency", then
+ a. Let list be ! AvailableCurrencies( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCurrencies ( )
+ The AvailableCurrencies abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique, well-formed, and upper case
+ canonicalized 3-letter ISO 4217 currency codes, identifying the currencies
+ for which the implementation provides the functionality of Intl.DisplayNames
+ and Intl.NumberFormat objects.
+locale: [en]
+features: [Intl-enumeration, Intl.DisplayNames, Array.prototype.includes]
+---*/
+
+const currencies = Intl.supportedValuesOf("currency");
+
+const obj = new Intl.DisplayNames("en", {type: "currency", fallback: "none"});
+
+for (let currency of currencies) {
+ assert.sameValue(typeof obj.of(currency), "string",
+ `${currency} is supported by DisplayNames`);
+}
+
+for (let i = 0x41; i <= 0x5A; ++i) {
+ for (let j = 0x41; j <= 0x5A; ++j) {
+ for (let k = 0x41; k <= 0x5A; ++k) {
+ let currency = String.fromCharCode(i, j, k);
+ if (typeof obj.of(currency) === "string") {
+ assert(currencies.includes(currency),
+ `${currency} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!currencies.includes(currency),
+ `${currency} not supported but returned by supportedValuesOf`);
+ }
+ }
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-NumberFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-NumberFormat.js
new file mode 100644
index 0000000000..089d0dd322
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies-accepted-by-NumberFormat.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "currency" values can be used with NumberFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 4. Else if key is "currency", then
+ a. Let list be ! AvailableCurrencies( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCurrencies ( )
+ The AvailableCurrencies abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique, well-formed, and upper case
+ canonicalized 3-letter ISO 4217 currency codes, identifying the currencies
+ for which the implementation provides the functionality of Intl.DisplayNames
+ and Intl.NumberFormat objects.
+locale: [en]
+features: [Intl-enumeration]
+---*/
+
+const currencies = Intl.supportedValuesOf("currency");
+
+for (let currency of currencies) {
+ let obj = new Intl.NumberFormat("en", {style: "currency", currency});
+ assert.sameValue(obj.resolvedOptions().currency, currency,
+ `${currency} is supported by NumberFormat`);
+}
+
+// Note: We can't test that additional currency values not present in |currencies|
+// aren't supported by Intl.NumberFormat, because PartitionNumberPattern defaults
+// to using the currency code itself when the currency is unsupported:
+//
+// PartitionNumberPattern, step 8.k.iii:
+// Let cd be an ILD String value representing currency after x in currencyDisplay form,
+// which may depend on x in languages having different plural forms. If the
+// implementation does not have such a representation of currency, use currency itself.
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies.js
new file mode 100644
index 0000000000..467ee39ca1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/currencies.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "currency" values are sorted, unique, and upper-case canonicalised.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 4. Else if key is "currency", then
+ a. Let list be ! AvailableCurrencies( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableCurrencies ( )
+ The AvailableCurrencies abstract operation returns a List, ordered as if an
+ Array of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains unique, well-formed, and upper case
+ canonicalized 3-letter ISO 4217 currency codes, identifying the currencies
+ for which the implementation provides the functionality of Intl.DisplayNames
+ and Intl.NumberFormat objects.
+includes: [compareArray.js]
+features: [Intl-enumeration]
+---*/
+
+const currencies = Intl.supportedValuesOf("currency");
+
+assert(Array.isArray(currencies), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(currencies), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherCurrencies = Intl.supportedValuesOf("currency");
+assert.notSameValue(otherCurrencies, currencies,
+ "Returns a new array object for each call.");
+
+assert.compareArray(currencies, otherCurrencies.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(currencies).size, currencies.length,
+ "The array doesn't contain duplicates.");
+
+const codeRE = /^[A-Z]{3}$/;
+for (let currency of currencies) {
+ assert(codeRE.test(currency), `${currency} is a 3-letter ISO 4217 currency code`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/invalid-key.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/invalid-key.js
new file mode 100644
index 0000000000..07202b4b5d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/invalid-key.js
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Intl.supportedValuesOf throws a RangeError if the key is invalid.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 8. Else,
+ a. Throw a RangeError exception.
+ ...
+features: [Intl-enumeration]
+---*/
+
+const invalidKeys = [
+ // Empty string is invalid.
+ "",
+
+ // Various unsupported keys.
+ "hourCycle", "locale", "language", "script", "region",
+
+ // Plural form of supported keys not valid.
+ "calendars", "collations", "currencies", "numberingSystems", "timeZones", "units",
+
+ // Wrong case for supported keys.
+ "CALENDAR", "Collation", "Currency", "numberingsystem", "timezone", "UNIT",
+
+ // NUL character must be handled correctly.
+ "calendar\0",
+
+ // Non-string cases.
+ undefined, null, false, true, NaN, 0, Math.PI, 123n, {}, [],
+];
+
+for (let key of invalidKeys) {
+ assert.throws(RangeError, function() {
+ Intl.supportedValuesOf(key);
+ }, "key: " + key);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/length.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/length.js
new file mode 100644
index 0000000000..39a5d577f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/length.js
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Intl.supportedValuesOf.length value and descriptor.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 18 ECMAScript Standard Built-in Objects:
+ Every built-in function object, including constructors, has a "length"
+ property whose value is a non-negative integral Number. Unless otherwise
+ specified, this value is equal to the number of required parameters shown in
+ the subclause heading for the function description. Optional parameters and
+ rest parameters are not included in the parameter count.
+
+ Unless otherwise specified, the "length" property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl-enumeration]
+---*/
+
+verifyProperty(Intl.supportedValuesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/name.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/name.js
new file mode 100644
index 0000000000..c8838bb249
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/name.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Intl.supportedValuesOf.name value and descriptor.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 18 ECMAScript Standard Built-in Objects:
+ Every built-in function object, including constructors, has a "name"
+ property whose value is a String. Unless otherwise specified, this value is
+ the name that is given to the function in this specification. Functions that
+ are identified as anonymous functions use the empty String as the value of
+ the "name" property. For functions that are specified as properties of
+ objects, the name value is the property name string used to access the
+ function.
+
+ Unless otherwise specified, the "name" property of a built-in function object
+ has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl-enumeration]
+---*/
+
+verifyProperty(Intl.supportedValuesOf, "name", {
+ value: "supportedValuesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-DateTimeFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-DateTimeFormat.js
new file mode 100644
index 0000000000..71c89eaa97
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-DateTimeFormat.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "numberingSystem" values can be used with DateTimeFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 5. Else if key is "numberingSystem", then
+ a. Let list be ! AvailableNumberingSystems( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableNumberingSystems ( )
+ The AvailableNumberingSystems abstract operation returns a List, ordered as
+ if an Array of the same values had been sorted using %Array.prototype.sort%
+ using undefined as comparefn, that contains unique numbering systems
+ identifiers identifying the numbering systems for which the implementation
+ provides the functionality of Intl.DateTimeFormat, Intl.NumberFormat, and
+ Intl.RelativeTimeFormat objects. The list must include the Numbering System
+ value of every row of Table 4, except the header row.
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+for (let numberingSystem of numberingSystems) {
+ let obj = new Intl.DateTimeFormat("en", {numberingSystem});
+ assert.sameValue(obj.resolvedOptions().numberingSystem, numberingSystem,
+ `${numberingSystem} is supported by DateTimeFormat`);
+}
+
+for (let numberingSystem of allNumberingSystems()) {
+ let obj = new Intl.DateTimeFormat("en", {numberingSystem});
+ if (obj.resolvedOptions().numberingSystem === numberingSystem) {
+ assert(numberingSystems.includes(numberingSystem),
+ `${numberingSystem} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!numberingSystems.includes(numberingSystem),
+ `${numberingSystem} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-NumberFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-NumberFormat.js
new file mode 100644
index 0000000000..877d28368c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-NumberFormat.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "numberingSystem" values can be used with NumberFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 5. Else if key is "numberingSystem", then
+ a. Let list be ! AvailableNumberingSystems( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableNumberingSystems ( )
+ The AvailableNumberingSystems abstract operation returns a List, ordered as
+ if an Array of the same values had been sorted using %Array.prototype.sort%
+ using undefined as comparefn, that contains unique numbering systems
+ identifiers identifying the numbering systems for which the implementation
+ provides the functionality of Intl.DateTimeFormat, Intl.NumberFormat, and
+ Intl.RelativeTimeFormat objects. The list must include the Numbering System
+ value of every row of Table 4, except the header row.
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+for (let numberingSystem of numberingSystems) {
+ let obj = new Intl.NumberFormat("en", {numberingSystem});
+ assert.sameValue(obj.resolvedOptions().numberingSystem, numberingSystem,
+ `${numberingSystem} is supported by NumberFormat`);
+}
+
+for (let numberingSystem of allNumberingSystems()) {
+ let obj = new Intl.NumberFormat("en", {numberingSystem});
+ if (obj.resolvedOptions().numberingSystem === numberingSystem) {
+ assert(numberingSystems.includes(numberingSystem),
+ `${numberingSystem} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!numberingSystems.includes(numberingSystem),
+ `${numberingSystem} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-RelativeTimeFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-RelativeTimeFormat.js
new file mode 100644
index 0000000000..423a3ae3dc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-accepted-by-RelativeTimeFormat.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "numberingSystem" values can be used with RelativeTimeFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 5. Else if key is "numberingSystem", then
+ a. Let list be ! AvailableNumberingSystems( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableNumberingSystems ( )
+ The AvailableNumberingSystems abstract operation returns a List, ordered as
+ if an Array of the same values had been sorted using %Array.prototype.sort%
+ using undefined as comparefn, that contains unique numbering systems
+ identifiers identifying the numbering systems for which the implementation
+ provides the functionality of Intl.DateTimeFormat, Intl.NumberFormat, and
+ Intl.RelativeTimeFormat objects. The list must include the Numbering System
+ value of every row of Table 4, except the header row.
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Intl.RelativeTimeFormat, Array.prototype.includes]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+for (let numberingSystem of numberingSystems) {
+ let obj = new Intl.RelativeTimeFormat("en", {numberingSystem});
+ assert.sameValue(obj.resolvedOptions().numberingSystem, numberingSystem,
+ `${numberingSystem} is supported by RelativeTimeFormat`);
+}
+
+for (let numberingSystem of allNumberingSystems()) {
+ let obj = new Intl.RelativeTimeFormat("en", {numberingSystem});
+ if (obj.resolvedOptions().numberingSystem === numberingSystem) {
+ assert(numberingSystems.includes(numberingSystem),
+ `${numberingSystem} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!numberingSystems.includes(numberingSystem),
+ `${numberingSystem} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js
new file mode 100644
index 0000000000..0ff107c350
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "numberingSystem" values contain all numbering systems with simple digit mappings.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 5. Else if key is "numberingSystem", then
+ a. Let list be ! AvailableNumberingSystems( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableNumberingSystems ( )
+ The AvailableNumberingSystems abstract operation returns a List, ordered as
+ if an Array of the same values had been sorted using %Array.prototype.sort%
+ using undefined as comparefn, that contains unique numbering systems
+ identifiers identifying the numbering systems for which the implementation
+ provides the functionality of Intl.DateTimeFormat, Intl.NumberFormat, and
+ Intl.RelativeTimeFormat objects. The list must include the Numbering System
+ value of every row of Table 4, except the header row.
+includes: [testIntl.js]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+// Table 10: Numbering systems with simple digit mappings
+for (let numberingSystem of Object.keys(numberingSystemDigits)) {
+ assert(numberingSystems.includes(numberingSystem),
+ `${numberingSystem} with simple digit mappings is supported`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems.js
new file mode 100644
index 0000000000..cb02432cd7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/numberingSystems.js
@@ -0,0 +1,57 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "numberingSystem" values are sorted, unique, and match the type production.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 5. Else if key is "numberingSystem", then
+ a. Let list be ! AvailableNumberingSystems( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableNumberingSystems ( )
+ The AvailableNumberingSystems abstract operation returns a List, ordered as
+ if an Array of the same values had been sorted using %Array.prototype.sort%
+ using undefined as comparefn, that contains unique numbering systems
+ identifiers identifying the numbering systems for which the implementation
+ provides the functionality of Intl.DateTimeFormat, Intl.NumberFormat, and
+ Intl.RelativeTimeFormat objects. The list must include the Numbering System
+ value of every row of Table 4, except the header row.
+includes: [compareArray.js]
+features: [Intl-enumeration, Intl.Locale]
+---*/
+
+const numberingSystems = Intl.supportedValuesOf("numberingSystem");
+
+assert(Array.isArray(numberingSystems), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(numberingSystems), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherNumberingSystems = Intl.supportedValuesOf("numberingSystem");
+assert.notSameValue(otherNumberingSystems, numberingSystems,
+ "Returns a new array object for each call.");
+
+assert.compareArray(numberingSystems, otherNumberingSystems.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(numberingSystems).size, numberingSystems.length,
+ "The array doesn't contain duplicates.");
+
+// https://unicode.org/reports/tr35/tr35.html#Unicode_locale_identifier
+const typeRE = /^[a-z0-9]{3,8}(-[a-z0-9]{3,8})*$/;
+for (let numberingSystem of numberingSystems) {
+ assert(typeRE.test(numberingSystem), `${numberingSystem} matches the 'type' production`);
+}
+
+for (let numberingSystem of numberingSystems) {
+ assert.sameValue(new Intl.Locale("und", {numberingSystem}).numberingSystem, numberingSystem,
+ `${numberingSystem} is canonicalised`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/prop-desc.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/prop-desc.js
new file mode 100644
index 0000000000..5722b8f3ac
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/prop-desc.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ Intl.supportedValuesOf property attributes.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 18 ECMAScript Standard Built-in Objects:
+ Every other data property described in clauses 19 through 28 and in Annex B.2
+ has the attributes { [[Writable]]: true, [[Enumerable]]: false,
+ [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl-enumeration]
+---*/
+
+verifyProperty(Intl, "supportedValuesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/shell.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones-accepted-by-DateTimeFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones-accepted-by-DateTimeFormat.js
new file mode 100644
index 0000000000..b2be51b737
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones-accepted-by-DateTimeFormat.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "timeZone" values can be used with DateTimeFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 6. Else if key is "timeZone", then
+ a. Let list be ! AvailableTimeZones( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableTimeZones ()
+ The AvailableTimeZones abstract operation returns a sorted List of supported
+ Zone and Link names in the IANA Time Zone Database. The following steps are
+ taken:
+
+ 1. Let names be a List of all supported Zone and Link names in the IANA Time
+ Zone Database.
+ 2. Let result be a new empty List.
+ 3. For each element name of names, do
+ a. Assert: ! IsValidTimeZoneName( name ) is true.
+ b. Let canonical be ! CanonicalizeTimeZoneName( name ).
+ c. If result does not contain an element equal to canonical, then
+ i. Append canonical to the end of result.
+ 4. Sort result in order as if an Array of the same values had been sorted using
+ %Array.prototype.sort% using undefined as comparefn.
+ 5. Return result.
+locale: [en]
+features: [Intl-enumeration]
+---*/
+
+const timeZones = Intl.supportedValuesOf("timeZone");
+
+for (let timeZone of timeZones) {
+ let obj = new Intl.DateTimeFormat("en", {timeZone});
+ assert.sameValue(obj.resolvedOptions().timeZone, timeZone,
+ `${timeZone} is supported by DateTimeFormat`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones.js
new file mode 100644
index 0000000000..0f12d4c9e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/timeZones.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "timeZone" values are sorted, unique, and canonicalised.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 6. Else if key is "timeZone", then
+ a. Let list be ! AvailableTimeZones( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableTimeZones ()
+ The AvailableTimeZones abstract operation returns a sorted List of supported
+ Zone and Link names in the IANA Time Zone Database. The following steps are
+ taken:
+
+ 1. Let names be a List of all supported Zone and Link names in the IANA Time
+ Zone Database.
+ 2. Let result be a new empty List.
+ 3. For each element name of names, do
+ a. Assert: ! IsValidTimeZoneName( name ) is true.
+ b. Let canonical be ! CanonicalizeTimeZoneName( name ).
+ c. If result does not contain an element equal to canonical, then
+ i. Append canonical to the end of result.
+ 4. Sort result in order as if an Array of the same values had been sorted using
+ %Array.prototype.sort% using undefined as comparefn.
+ 5. Return result.
+includes: [compareArray.js, testIntl.js]
+features: [Intl-enumeration]
+---*/
+
+const timeZones = Intl.supportedValuesOf("timeZone");
+
+assert(Array.isArray(timeZones), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(timeZones), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherTimeZones = Intl.supportedValuesOf("timeZone");
+assert.notSameValue(otherTimeZones, timeZones,
+ "Returns a new array object for each call.");
+
+assert.compareArray(timeZones, otherTimeZones.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(timeZones).size, timeZones.length,
+ "The array doesn't contain duplicates.");
+
+for (let timeZone of timeZones) {
+ assert(isCanonicalizedStructurallyValidTimeZoneName(timeZone),
+ `${timeZone} is a canonicalised and structurally valid time zone name`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/units-accepted-by-NumberFormat.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/units-accepted-by-NumberFormat.js
new file mode 100644
index 0000000000..0132f569b2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/units-accepted-by-NumberFormat.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "unit" values can be used with NumberFormat.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 7. Else if key is "unit", then
+ a. Let list be ! AvailableUnits( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableUnits ( )
+ The AvailableUnits abstract operation returns a List, ordered as if an Array
+ of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains the unique values of simple unit
+ identifiers listed in every row of Table 1, except the header row.
+includes: [testIntl.js]
+locale: [en]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const units = Intl.supportedValuesOf("unit");
+
+for (let unit of units) {
+ let obj = new Intl.NumberFormat("en", {style: "unit", unit});
+ assert.sameValue(obj.resolvedOptions().unit, unit,
+ `${unit} is supported by NumberFormat`);
+}
+
+for (let unit of allSimpleSanctionedUnits()) {
+ let obj = new Intl.NumberFormat("en", {style: "unit", unit});
+ if (obj.resolvedOptions().unit === unit) {
+ assert(units.includes(unit),
+ `${unit} supported but not returned by supportedValuesOf`);
+ } else {
+ assert(!units.includes(unit),
+ `${unit} not supported but returned by supportedValuesOf`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/supportedValuesOf/units.js b/js/src/tests/test262/intl402/Intl/supportedValuesOf/units.js
new file mode 100644
index 0000000000..9f1084bdf0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/supportedValuesOf/units.js
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.supportedvaluesof
+description: >
+ The returned "unit" values are sorted, unique, and well-formed.
+info: |
+ Intl.supportedValuesOf ( key )
+
+ 1. Let key be ? ToString(key).
+ ...
+ 7. Else if key is "unit", then
+ a. Let list be ! AvailableUnits( ).
+ ...
+ 9. Return ! CreateArrayFromList( list ).
+
+ AvailableUnits ( )
+ The AvailableUnits abstract operation returns a List, ordered as if an Array
+ of the same values had been sorted using %Array.prototype.sort% using
+ undefined as comparefn, that contains the unique values of simple unit
+ identifiers listed in every row of Table 1, except the header row.
+includes: [compareArray.js, testIntl.js]
+features: [Intl-enumeration, Array.prototype.includes]
+---*/
+
+const units = Intl.supportedValuesOf("unit");
+
+assert(Array.isArray(units), "Returns an Array object.");
+assert.sameValue(Object.getPrototypeOf(units), Array.prototype,
+ "The array prototype is Array.prototype");
+
+const otherUnits = Intl.supportedValuesOf("unit");
+assert.notSameValue(otherUnits, units,
+ "Returns a new array object for each call.");
+
+assert.compareArray(units, otherUnits.sort(),
+ "The array is sorted.");
+
+assert.sameValue(new Set(units).size, units.length,
+ "The array doesn't contain duplicates.");
+
+const simpleSanctioned = allSimpleSanctionedUnits();
+
+for (let unit of units) {
+ assert(simpleSanctioned.includes(unit), `${unit} is a simple, sanctioned unit`);
+ assert(!unit.includes("-per-"), `${unit} isn't a compound unit`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/toStringTag/browser.js b/js/src/tests/test262/intl402/Intl/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/Intl/toStringTag/shell.js b/js/src/tests/test262/intl402/Intl/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/Intl/toStringTag/toString.js b/js/src/tests/test262/intl402/Intl/toStringTag/toString.js
new file mode 100644
index 0000000000..fdcd994990
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/toStringTag/toString.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl-toStringTag
+description: >
+ Object.prototype.toString utilizes Intl[@@toStringTag] and doesn't special-case Intl namespace object.
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl".
+features: [Symbol.toStringTag]
+---*/
+
+assert.sameValue(Intl.toString(), "[object Intl]");
+assert.sameValue(Object.prototype.toString.call(Intl), "[object Intl]");
+
+Object.defineProperty(Intl, Symbol.toStringTag, { value: "test262" });
+assert.sameValue(Intl.toString(), "[object test262]");
+assert.sameValue(Object.prototype.toString.call(Intl), "[object test262]");
+
+assert(delete Intl[Symbol.toStringTag]);
+assert.sameValue(Intl.toString(), "[object Object]");
+assert.sameValue(Object.prototype.toString.call(Intl), "[object Object]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Intl/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/Intl/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..0b5c250369
--- /dev/null
+++ b/js/src/tests/test262/intl402/Intl/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl-toStringTag
+description: >
+ Property descriptor of Intl[@@toStringTag].
+info: |
+ Intl [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, Symbol.toStringTag, {
+ value: "Intl",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/browser.js b/js/src/tests/test262/intl402/ListFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/browser.js b/js/src/tests/test262/intl402/ListFormat/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/browser.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-invalid.js
new file mode 100644
index 0000000000..8cb49d6243
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-invalid.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks error cases for the locales argument to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+includes: [testIntl.js]
+features: [Intl.ListFormat]
+---*/
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, function() { new Intl.ListFormat(locales) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-valid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-valid.js
new file mode 100644
index 0000000000..4a63cec4c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/locales-valid.js
@@ -0,0 +1,45 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks various cases for the locales argument to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+features: [Intl.ListFormat]
+---*/
+
+const defaultLocale = new Intl.ListFormat().resolvedOptions().locale;
+
+const tests = [
+ [undefined, defaultLocale, "undefined"],
+ ["EN", "en", "Single value"],
+ [[], defaultLocale, "Empty array"],
+ [["en", "EN"], "en", "Duplicate value (canonical first)"],
+ [["EN", "en"], "en", "Duplicate value (canonical last)"],
+ [{ 0: "DE", length: 0 }, defaultLocale, "Object with zero length"],
+ [{ 0: "DE", length: 1 }, "de", "Object with length"],
+];
+
+const errorTests = [
+ [["en-GB-oed"], "Grandfathered"],
+ [["x-private"], "Private", ["lookup"]],
+];
+
+for (const [locales, expected, name, matchers = ["lookup", "best fit"]] of tests) {
+ for (const matcher of matchers) {
+ const rtf = new Intl.ListFormat(locales, {localeMatcher: matcher});
+ assert.sameValue(rtf.resolvedOptions().locale, expected, name);
+ }
+}
+
+for (const [locales, name, matchers = ["lookup", "best fit"]] of errorTests) {
+ for (const matcher of matchers) {
+ assert.throws(RangeError, function() {
+ new Intl.ListFormat(locales, {localeMatcher: matcher})
+ }, name);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/newtarget-undefined.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/newtarget-undefined.js
new file mode 100644
index 0000000000..48198c9a92
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/newtarget-undefined.js
@@ -0,0 +1,29 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.ListFormat
+description: >
+ Verifies the NewTarget check for Intl.ListFormat.
+info: |
+ Intl.ListFormat ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat, "function");
+
+assert.throws(TypeError, function() {
+ Intl.ListFormat();
+});
+
+assert.throws(TypeError, function() {
+ Intl.ListFormat("en");
+});
+
+assert.throws(TypeError, function() {
+ Intl.ListFormat("not-valid-tag");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-getoptionsobject.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-getoptionsobject.js
new file mode 100644
index 0000000000..df916e1d6a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-getoptionsobject.js
@@ -0,0 +1,26 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of non-object option arguments to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+features: [Intl.ListFormat,BigInt]
+---*/
+
+const optionsArguments = [
+ null,
+ true,
+ false,
+ "test",
+ 7,
+ Symbol(),
+ 123456789n,
+];
+
+for (const options of optionsArguments) {
+ assert.throws(TypeError, function() { new Intl.ListFormat([], options) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-invalid.js
new file mode 100644
index 0000000000..681f5f1c96
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-invalid.js
@@ -0,0 +1,19 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of a null options argument to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 3. Else
+ a. Let options be ? ToObject(options).
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat, "function");
+assert.throws(TypeError, function() {
+ new Intl.ListFormat([], null);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..19d643f2d9
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-localeMatcher-invalid.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of invalid value for the localeMatcher option to the ListFormat constructor.
+info: |
+ Intl.ListFormat ( [ locales [ , options ] ] )
+ 12. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+features: [Intl.ListFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const localeMatcher of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.ListFormat([], { localeMatcher });
+ }, `${localeMatcher} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-order.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-order.js
new file mode 100644
index 0000000000..3d6a9e11e6
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-order.js
@@ -0,0 +1,59 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks the order of operations on the options argument to the ListFormat constructor.
+info: |
+ Intl.ListFormat ( [ locales [ , options ] ] )
+ 7. Let type be GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
+ 9. Let style be GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
+ 12. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+includes: [compareArray.js]
+features: [Intl.ListFormat]
+---*/
+
+const callOrder = [];
+
+new Intl.ListFormat([], {
+ get localeMatcher() {
+ callOrder.push("localeMatcher");
+ return {
+ toString() {
+ callOrder.push("localeMatcher toString");
+ return "best fit";
+ }
+ };
+ },
+
+ get type() {
+ callOrder.push("type");
+ return {
+ toString() {
+ callOrder.push("type toString");
+ return "unit";
+ }
+ };
+ },
+
+ get style() {
+ callOrder.push("style");
+ return {
+ toString() {
+ callOrder.push("style toString");
+ return "short";
+ }
+ };
+ },
+});
+
+assert.compareArray(callOrder, [
+ "localeMatcher",
+ "localeMatcher toString",
+ "type",
+ "type toString",
+ "style",
+ "style toString",
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-invalid.js
new file mode 100644
index 0000000000..6701776cd7
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-invalid.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of invalid value for the style option to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 9. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
+features: [Intl.ListFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Long",
+ "LONG",
+ "long\0",
+ "Short",
+ "SHORT",
+ "short\0",
+ "Narrow",
+ "NARROW",
+ "narrow\0",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.ListFormat([], {"style": invalidOption});
+ }, `${invalidOption} is an invalid style option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-valid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-valid.js
new file mode 100644
index 0000000000..bc97773d33
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-style-valid.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of valid values for the style option to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ InitializeListFormat (listFormat, locales, options)
+ 12. Let type be ? GetOption(options, "type", "string", « "conjunction",
+ "disjunction", "unit" », "conjunction").
+ 13. Set listFormat.[[Type]] to type.
+ 14. Let style be ? GetOption(options, "style", "string", « "long", "short",
+ "narrow" », "long").
+ 15. Set listFormat.[[Style]] to style.
+features: [Intl.ListFormat]
+---*/
+
+const validOptions = [
+ [undefined, "long"],
+ ["long", "long"],
+ ["short", "short"],
+ ["narrow", "narrow"],
+ [{ toString() { return "short"; } }, "short"],
+ [{ toString() { return "long"; } }, "long"],
+ [{ toString() { return "narrow"; } }, "narrow"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const lf = new Intl.ListFormat([], {"style": validOption});
+ const resolvedOptions = lf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-throwing-getters.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-throwing-getters.js
new file mode 100644
index 0000000000..3da4b38e1a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-throwing-getters.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks the propagation of exceptions from the options for the ListFormat constructor.
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+const options = [
+ "type",
+ "style",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.ListFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-invalid.js
new file mode 100644
index 0000000000..10f6fd599d
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-invalid.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of invalid value for the type option to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 7. Let type be GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
+features: [Intl.ListFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Conjunction",
+ "CONJUNCTION",
+ "conjunction\0",
+ "Disjunction",
+ "DISJUNCTION",
+ "disjunction\0",
+ "Unit",
+ "UNIT",
+ "unit\0",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.ListFormat([], {"type": invalidOption});
+ }, `${invalidOption} is an invalid type option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-valid.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-valid.js
new file mode 100644
index 0000000000..e3ba4e1077
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-type-valid.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of valid values for the style option to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+ 7. Let type be GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
+ 8. Set listFormat.[[Type]] to type.
+features: [Intl.ListFormat]
+---*/
+
+const validOptions = [
+ [undefined, "conjunction"],
+ ["conjunction", "conjunction"],
+ ["disjunction", "disjunction"],
+ ["unit", "unit"],
+ [{ toString() { return "unit"; } }, "unit"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const lf = new Intl.ListFormat([], {"type": validOption});
+ const resolvedOptions = lf.resolvedOptions();
+ assert.sameValue(resolvedOptions.type, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-undefined.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-undefined.js
new file mode 100644
index 0000000000..56768c9053
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/options-undefined.js
@@ -0,0 +1,40 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks handling of non-object option arguments to the ListFormat constructor.
+info: |
+ InitializeListFormat (listFormat, locales, options)
+features: [Intl.ListFormat]
+---*/
+
+Object.defineProperties(Object.prototype, {
+ "type": {
+ get() {
+ throw new Error("Should not call type getter");
+ }
+ },
+ "style": {
+ get() {
+ throw new Error("Should not call style getter");
+ }
+ },
+})
+
+const optionsArguments = [
+ [],
+ [[]],
+ [[], undefined],
+];
+
+for (const args of optionsArguments) {
+ const lf = new Intl.ListFormat(...args);
+ const resolvedOptions = lf.resolvedOptions();
+ assert.sameValue(resolvedOptions.type, "conjunction",
+ `Calling with ${args.length} empty arguments should yield the correct value for "type"`);
+ assert.sameValue(resolvedOptions.style, "long",
+ `Calling with ${args.length} empty arguments should yield the correct value for "style"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..44862fa12a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/proto-from-ctor-realm.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.ListFormat ( [ locales [ , options ] ] )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormatPrototype%", « ... »).
+ ...
+ 24. Return listFormat.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.ListFormat, cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var lf;
+
+newTarget.prototype = undefined;
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = true;
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = NaN;
+lf = Reflect.construct(Intl.ListFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(lf), other.Intl.ListFormat.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/shell.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/constructor/subclassing.js b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/subclassing.js
new file mode 100644
index 0000000000..1fd6553f9b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/constructor/subclassing.js
@@ -0,0 +1,41 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Checks that ListFormat can be subclassed.
+info: |
+ Intl.ListFormat ( [ locales [ , options ] ] )
+
+ 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormatPrototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]] »).
+
+features: [Intl.ListFormat]
+---*/
+
+class CustomListFormat extends Intl.ListFormat {
+ constructor(locales, options) {
+ super(locales, options);
+ this.isCustom = true;
+ }
+}
+
+const locale = "de";
+const argument = ["foo", "bar"];
+
+const real_lf = new Intl.ListFormat(locale);
+assert.sameValue(real_lf.isCustom, undefined, "Custom property");
+
+const custom_lf = new CustomListFormat(locale);
+assert.sameValue(custom_lf.isCustom, true, "Custom property");
+
+assert.sameValue(custom_lf.format(argument),
+ real_lf.format(argument),
+ "Direct call");
+
+assert.sameValue(Intl.ListFormat.prototype.format.call(custom_lf, argument),
+ Intl.ListFormat.prototype.format.call(real_lf, argument),
+ "Indirect call");
+
+assert.sameValue(Object.getPrototypeOf(custom_lf), CustomListFormat.prototype, "Prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/length.js b/js/src/tests/test262/intl402/ListFormat/constructor/length.js
new file mode 100644
index 0000000000..19aa2852db
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: >
+ Checks the "length" property of the ListFormat constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The ListFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/name.js b/js/src/tests/test262/intl402/ListFormat/constructor/name.js
new file mode 100644
index 0000000000..f498bb5e0f
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: >
+ Checks the "name" property of the ListFormat constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat, "name", {
+ value: "ListFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/constructor/prop-desc.js
new file mode 100644
index 0000000000..3bf5b526fa
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/prop-desc.js
@@ -0,0 +1,37 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: >
+ "ListFormat" property of Intl.
+info: |
+ Intl.ListFormat (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat, "function");
+
+verifyProperty(Intl, "ListFormat", {
+ value: Intl.ListFormat,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/prototype.js b/js/src/tests/test262/intl402/ListFormat/constructor/prototype.js
new file mode 100644
index 0000000000..a09c23e176
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/prototype.js
@@ -0,0 +1,19 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: The prototype of the Intl.ListFormat constructor is %FunctionPrototype%.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Unless otherwise specified every built-in function object has the %FunctionPrototype% object as the initial value of its [[Prototype]] internal slot.
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ Object.getPrototypeOf(Intl.ListFormat),
+ Function.prototype,
+ "Object.getPrototypeOf(Intl.ListFormat) equals the value of Function.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/shell.js b/js/src/tests/test262/intl402/ListFormat/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..81b7dbcf88
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/basic.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Google Inc., Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: >
+ Tests that Intl.ListFormat has a supportedLocalesOf property,
+ and it works as planned.
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "supportedLocalesOf should be supported.");
+
+const defaultLocale = new Intl.ListFormat().resolvedOptions().locale;
+const notSupported = 'zxx'; // "no linguistic content"
+const requestedLocales = [defaultLocale, notSupported];
+
+const supportedLocales = Intl.ListFormat.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/branding.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/branding.js
new file mode 100644
index 0000000000..dd28fcf8d6
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/branding.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: >
+ Verifies there's no branding check for Intl.ListFormat.supportedLocalesOf().
+info: |
+ Intl.ListFormat.supportedLocalesOf ( locales [, options ])
+features: [Intl.ListFormat]
+---*/
+
+const fn = Intl.ListFormat.supportedLocalesOf;
+const thisValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.ListFormat,
+ Intl.ListFormat.prototype,
+];
+
+for (const thisValue of thisValues) {
+ const result = fn.call(thisValue);
+ assert.sameValue(Array.isArray(result), true);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..344194999a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: >
+ Checks the "length" property of Intl.ListFormat.supportedLocalesOf().
+info: |
+ The value of the length property of the supportedLocalesOf method is 1.
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, has a length property whose value is an integer.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/locales-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/locales-invalid.js
new file mode 100644
index 0000000000..f8d7769b84
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/locales-invalid.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Checks error cases for the locales argument to the supportedLocalesOf function.
+info: |
+ Intl.ListFormat.supportedLocalesOf ( locales [, options ])
+
+ 2. Let requestedLocales be CanonicalizeLocaleList(locales).
+includes: [testIntl.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "Should support Intl.ListFormat.supportedLocalesOf.");
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, () => Intl.ListFormat.supportedLocalesOf(locales));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..b4e3330d78
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: >
+ Checks the "name" property of Intl.ListFormat.supportedLocalesOf().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..99714f0967
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
@@ -0,0 +1,36 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Checks handling of invalid values for the localeMatcher option to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "Should support Intl.ListFormat.supportedLocalesOf.");
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ Intl.ListFormat.supportedLocalesOf([], {"localeMatcher": invalidOption});
+ }, `${invalidOption} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-null.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-null.js
new file mode 100644
index 0000000000..dbc7036579
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-null.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Checks handling of a null options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "Should support Intl.ListFormat.supportedLocalesOf.");
+
+assert.throws(TypeError, function() {
+ Intl.ListFormat.supportedLocalesOf([], null);
+}, "Should throw when passing null as the options argument");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-toobject.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-toobject.js
new file mode 100644
index 0000000000..811b1210b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-toobject.js
@@ -0,0 +1,43 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Checks handling of non-object options arguments to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "Should support Intl.ListFormat.supportedLocalesOf.");
+
+let called;
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() {
+ ++called;
+ return "best fit";
+ }
+ }
+});
+
+const optionsArguments = [
+ true,
+ "test",
+ 7,
+ Symbol(),
+];
+
+for (const options of optionsArguments) {
+ called = 0;
+ const result = Intl.ListFormat.supportedLocalesOf([], options);
+ assert.sameValue(Array.isArray(result), true, `Expected array from ${String(options)}`);
+ assert.sameValue(called, 1, `Expected one call from ${String(options)}`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-undefined.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-undefined.js
new file mode 100644
index 0000000000..3c9506163d
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/options-undefined.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Checks handling of an undefined options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(typeof Intl.ListFormat.supportedLocalesOf, "function",
+ "Should support Intl.ListFormat.supportedLocalesOf.");
+
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() { throw new Error("Should not call localeMatcher getter"); }
+ }
+});
+
+assert.sameValue(Array.isArray(Intl.ListFormat.supportedLocalesOf()), true, "No arguments");
+assert.sameValue(Array.isArray(Intl.ListFormat.supportedLocalesOf([])), true, "One argument");
+assert.sameValue(Array.isArray(Intl.ListFormat.supportedLocalesOf([], undefined)), true, "Two arguments");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..07455d691d
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: >
+ Checks the "supportedLocalesOf" property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.supportedLocalesOf ( locales [, options ])
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.ListFormat.supportedLocalesOf,
+ "function",
+ "typeof Intl.ListFormat.supportedLocalesOf is function"
+);
+
+verifyProperty(Intl.ListFormat, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/result-type.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/result-type.js
new file mode 100644
index 0000000000..e6d25bbd95
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/result-type.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.supportedLocalesOf
+description: Verifies the type of the return value of Intl.ListFormat.supportedLocalesOf().
+info: |
+ Intl.ListFormat.supportedLocalesOf ( locales [, options ])
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+const result = Intl.ListFormat.supportedLocalesOf("en");
+assert.sameValue(Array.isArray(result), true,
+ "Array.isArray() should return true");
+assert.sameValue(Object.getPrototypeOf(result), Array.prototype,
+ "The prototype should be Array.prototype");
+assert.sameValue(Object.isExtensible(result), true,
+ "Object.isExtensible() should return true");
+
+assert.notSameValue(result.length, 0);
+for (let i = 0; i < result.length; ++i) {
+ verifyProperty(result, String(i), {
+ "writable": true,
+ "enumerable": true,
+ "configurable": true,
+ });
+}
+
+verifyProperty(result, "length", {
+ "enumerable": false,
+ "configurable": false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/constructor/supportedLocalesOf/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/instance/browser.js b/js/src/tests/test262/intl402/ListFormat/instance/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/instance/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/instance/extensibility.js b/js/src/tests/test262/intl402/ListFormat/instance/extensibility.js
new file mode 100644
index 0000000000..b08bca5c69
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/instance/extensibility.js
@@ -0,0 +1,21 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Intl.ListFormat instance object extensibility
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Unless specified otherwise, the [[Extensible]] internal slot
+ of a built-in object initially has the value true.
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ Object.isExtensible(new Intl.ListFormat()),
+ true,
+ "Object.isExtensible(new Intl.ListFormat()) returns true"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/instance/prototype.js b/js/src/tests/test262/intl402/ListFormat/instance/prototype.js
new file mode 100644
index 0000000000..123fa04b38
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/instance/prototype.js
@@ -0,0 +1,21 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat
+description: Intl.ListFormat instance object is created from %ListFormatPrototype%.
+info: |
+ Intl.ListFormat ([ locales [ , options ]])
+
+ 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormatPrototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]] »).
+features: [Intl.ListFormat]
+---*/
+
+const value = new Intl.ListFormat();
+assert.sameValue(
+ Object.getPrototypeOf(value),
+ Intl.ListFormat.prototype,
+ "Object.getPrototypeOf(value) equals the value of Intl.ListFormat.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/instance/shell.js b/js/src/tests/test262/intl402/ListFormat/instance/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/instance/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/constructor/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..882657f4a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/prop-desc.js
@@ -0,0 +1,26 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.constructor
+description: Checks the "constructor" property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.constructor
+
+ The initial value of Intl.ListFormat.prototype.constructor is %ListFormat%.
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype, "constructor", {
+ value: Intl.ListFormat,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/constructor/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/branding.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/branding.js
new file mode 100644
index 0000000000..49fed5952f
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/branding.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Verifies the branding check for the "format" function of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.format ([ list ])
+
+ 2. If Type(lf) is not Object, throw a TypeError exception.
+ 3. If lf does not have an [[InitializedListFormat]] internal slot, throw a TypeError exception.
+features: [Intl.ListFormat]
+---*/
+
+const format = Intl.ListFormat.prototype.format;
+
+assert.sameValue(typeof format, "function");
+
+assert.throws(TypeError, () => format.call(undefined), "undefined");
+assert.throws(TypeError, () => format.call(null), "null");
+assert.throws(TypeError, () => format.call(true), "true");
+assert.throws(TypeError, () => format.call(""), "empty string");
+assert.throws(TypeError, () => format.call(Symbol()), "symbol");
+assert.throws(TypeError, () => format.call(1), "1");
+assert.throws(TypeError, () => format.call({}), "plain object");
+assert.throws(TypeError, () => format.call(Intl.ListFormat), "Intl.ListFormat");
+assert.throws(TypeError, () => format.call(Intl.ListFormat.prototype), "Intl.ListFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-default.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-default.js
new file mode 100644
index 0000000000..788ba9594a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-default.js
@@ -0,0 +1,54 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US");
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo and bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar, and baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz, and quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o, and o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-disjunction.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-disjunction.js
new file mode 100644
index 0000000000..fb1e941d9a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-disjunction.js
@@ -0,0 +1,56 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "type": "disjunction",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo or bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar, or baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz, or quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o, or o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-narrow.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-narrow.js
new file mode 100644
index 0000000000..ef9798b787
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-narrow.js
@@ -0,0 +1,57 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "style": "narrow",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo bar baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo bar baz quux");
+}
+
+assert.sameValue(lf.format("foo"), "f o o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-short.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-short.js
new file mode 100644
index 0000000000..f56a9f23c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-short.js
@@ -0,0 +1,56 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "style": "short",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo & bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar, & baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz, & quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o, & o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-unit.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-unit.js
new file mode 100644
index 0000000000..da881123b8
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/en-us-unit.js
@@ -0,0 +1,56 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo, bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar, baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz, quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o, o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-long.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-long.js
new file mode 100644
index 0000000000..a4bfa6b425
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-long.js
@@ -0,0 +1,57 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "long",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo y bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar y baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz y quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o y o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-narrow.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-narrow.js
new file mode 100644
index 0000000000..cfd3f7b545
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-narrow.js
@@ -0,0 +1,57 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "narrow",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo bar baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo bar baz quux");
+}
+
+assert.sameValue(lf.format("foo"), "f o o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-short.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-short.js
new file mode 100644
index 0000000000..b812906d1b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/es-es-short.js
@@ -0,0 +1,57 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.format() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "short",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.format, "function", "format should be supported");
+
+for (const f of transforms) {
+ assert.sameValue(lf.format(f([])), "");
+ assert.sameValue(lf.format(f(["foo"])), "foo");
+ assert.sameValue(lf.format(f(["foo", "bar"])), "foo y bar");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz"])), "foo, bar, baz");
+ assert.sameValue(lf.format(f(["foo", "bar", "baz", "quux"])), "foo, bar, baz, quux");
+}
+
+assert.sameValue(lf.format("foo"), "f, o, o");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-getiterator-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-getiterator-throw.js
new file mode 100644
index 0000000000..88d701a832
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-getiterator-throw.js
@@ -0,0 +1,29 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable called by Intl.ListFormat.prototype.format() while the GetIterator
+ throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let get_iterator_throw_error = {
+ [Symbol.iterator]() {
+ throw new CustomError();
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.format(get_iterator_throw_error)});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-invalid.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-invalid.js
new file mode 100644
index 0000000000..59f274d2d1
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-invalid.js
@@ -0,0 +1,51 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format().
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterable_of_strings_and_number = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done:false, value: 3};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(TypeError,
+ ()=> {lf.format(iterable_of_strings_and_number)},
+ "Iterable yielded 3 which is not a string");
+assert.sameValue(iterable_of_strings_and_number.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorclose.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorclose.js
new file mode 100644
index 0000000000..f5fdc77d09
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorclose.js
@@ -0,0 +1,58 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format() and check the IteratorClose
+ calls return.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_close_call_return = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ "return"() {
+ this.returnIsCalled = true;
+ assert.sameValue(this.count, 3);
+ },
+ count: 0,
+ returnIsCalled: false,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done:false, value: 3};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(TypeError,
+ ()=> {lf.format(iterator_close_call_return)},
+ "Iterable yielded 3 which is not a string");
+assert.sameValue(iterator_close_call_return.count, 3);
+assert.sameValue(iterator_close_call_return.returnIsCalled, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorstep-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorstep-throw.js
new file mode 100644
index 0000000000..3a6f1fc5ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorstep-throw.js
@@ -0,0 +1,44 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format() while iteratorStep throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+features: [Intl.ListFormat]
+---*/
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_step_throw = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ throw new CustomError();
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.format(iterator_step_throw)});
+assert.sameValue(iterator_step_throw.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorvalue-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorvalue-throw.js
new file mode 100644
index 0000000000..3164b68630
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-iteratorvalue-throw.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format() while iteratorValue throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_value_throw = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done: false, get value() { throw new CustomError() }};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.format(iterator_value_throw)});
+assert.sameValue(iterator_value_throw.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-undefined.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-undefined.js
new file mode 100644
index 0000000000..9c922eb499
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable-undefined.js
@@ -0,0 +1,20 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format(undefined).
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+
+assert.sameValue("", lf.format(undefined));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable.js
new file mode 100644
index 0000000000..8b34bcb16c
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/iterable.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.format().
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+
+// Test the success case.
+let iterable_of_strings = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++
+ if (this.count < 4) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+lf.format(iterable_of_strings);
+assert.sameValue(iterable_of_strings.count, 4);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/length.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/length.js
new file mode 100644
index 0000000000..d4432129d6
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the "length" property of Intl.ListFormat.prototype.format().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The ListFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.format, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/name.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/name.js
new file mode 100644
index 0000000000..fe190330c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the "name" property of Intl.ListFormat.prototype.format().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.format, "name", {
+ value: "format",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/prop-desc.js
new file mode 100644
index 0000000000..0df402136f
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/prop-desc.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the "format" property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.format ([ list ])
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.ListFormat.prototype.format,
+ "function",
+ "typeof Intl.ListFormat.prototype.format is function"
+);
+
+verifyProperty(Intl.ListFormat.prototype, "format", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/format/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/format/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/format/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/branding.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/branding.js
new file mode 100644
index 0000000000..6b7473cb0e
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/branding.js
@@ -0,0 +1,29 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Verifies the branding check for the "formatToParts" function of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.formatToParts([ list ])
+
+ 2. If Type(lf) is not Object, throw a TypeError exception.
+ 3. If lf does not have an [[InitializedListFormat]] internal slot, throw a TypeError exception.
+features: [Intl.ListFormat]
+---*/
+
+const formatToParts = Intl.ListFormat.prototype.formatToParts;
+
+assert.sameValue(typeof formatToParts, "function");
+assert.throws(TypeError, () => formatToParts.call(undefined), "undefined");
+assert.throws(TypeError, () => formatToParts.call(null), "null");
+assert.throws(TypeError, () => formatToParts.call(true), "true");
+assert.throws(TypeError, () => formatToParts.call(""), "empty string");
+assert.throws(TypeError, () => formatToParts.call(Symbol()), "symbol");
+assert.throws(TypeError, () => formatToParts.call(1), "1");
+assert.throws(TypeError, () => formatToParts.call({}), "plain object");
+assert.throws(TypeError, () => formatToParts.call(Intl.ListFormat), "Intl.ListFormat");
+assert.throws(TypeError, () => formatToParts.call(Intl.ListFormat.prototype), "Intl.ListFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-default.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-default.js
new file mode 100644
index 0000000000..fc0cb7e7d4
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-default.js
@@ -0,0 +1,89 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US");
+
+assert.sameValue(typeof lf.formatToParts, "function", "formatToParts should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " and " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", and " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": ", and " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": ", and " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-disjunction.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-disjunction.js
new file mode 100644
index 0000000000..10756cb443
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-disjunction.js
@@ -0,0 +1,89 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", { "type": "disjunction", });
+
+assert.sameValue(typeof lf.formatToParts, "function", "formatToParts should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " or " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", or " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": ", or " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": ", or " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-narrow.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-narrow.js
new file mode 100644
index 0000000000..405559e49b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-narrow.js
@@ -0,0 +1,92 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "style": "narrow",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "formatToParts should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-short.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-short.js
new file mode 100644
index 0000000000..8934cda502
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-short.js
@@ -0,0 +1,91 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "style": "short",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "format should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " & " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", & " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": ", & " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": ", & " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-unit.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-unit.js
new file mode 100644
index 0000000000..2abe64951a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/en-us-unit.js
@@ -0,0 +1,91 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("en-US", {
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "format should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-long.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-long.js
new file mode 100644
index 0000000000..f6332de215
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-long.js
@@ -0,0 +1,92 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "long",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "format should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " y " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": " y " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": " y " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": " y " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-narrow.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-narrow.js
new file mode 100644
index 0000000000..ffd14fe622
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-narrow.js
@@ -0,0 +1,92 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "narrow",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "format should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": " " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-short.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-short.js
new file mode 100644
index 0000000000..bc996bbcae
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/es-es-short.js
@@ -0,0 +1,92 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Intl.ListFormat.prototype.formatToParts() in English.
+features: [Intl.ListFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+function CustomIterator(array) {
+ this.i = 0;
+ this.array = array;
+}
+
+CustomIterator.prototype[Symbol.iterator] = function() {
+ return this;
+}
+
+CustomIterator.prototype.next = function() {
+ if (this.i >= this.array.length) {
+ return {
+ "done": true,
+ };
+ }
+
+ return {
+ "value": this.array[this.i++],
+ "done": false,
+ };
+}
+
+const transforms = [
+ a => a,
+ a => a[Symbol.iterator](),
+ a => new CustomIterator(a),
+];
+
+const lf = new Intl.ListFormat("es-ES", {
+ "style": "short",
+ "type": "unit",
+});
+
+assert.sameValue(typeof lf.formatToParts, "function", "format should be supported");
+
+for (const f of transforms) {
+ verifyFormatParts(lf.formatToParts(f([])), []);
+ verifyFormatParts(lf.formatToParts(f(["foo"])), [
+ { "type": "element", "value": "foo" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": " y " },
+ { "type": "element", "value": "bar" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ ]);
+ verifyFormatParts(lf.formatToParts(f(["foo", "bar", "baz", "quux"])), [
+ { "type": "element", "value": "foo" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "bar" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "baz" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "quux" },
+ ]);
+}
+
+verifyFormatParts(lf.formatToParts("foo"), [
+ { "type": "element", "value": "f" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+ { "type": "literal", "value": ", " },
+ { "type": "element", "value": "o" },
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-getiterator-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-getiterator-throw.js
new file mode 100644
index 0000000000..2d3583018d
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-getiterator-throw.js
@@ -0,0 +1,30 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts() while the GetIterator
+ throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let get_iterator_throw_error = {
+ [Symbol.iterator]() {
+ throw new CustomError();
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.formatToParts(get_iterator_throw_error)});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-invalid.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-invalid.js
new file mode 100644
index 0000000000..71e286608b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-invalid.js
@@ -0,0 +1,51 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts().
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterable_of_strings_and_number = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done:false, value: 3};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(TypeError,
+ ()=> {lf.formatToParts(iterable_of_strings_and_number)},
+ "Iterable yielded 3 which is not a string");
+assert.sameValue(iterable_of_strings_and_number.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorclose.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorclose.js
new file mode 100644
index 0000000000..07912d085a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorclose.js
@@ -0,0 +1,58 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts() and check the IteratorClose
+ calls return.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_close_call_return = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ "return"() {
+ this.returnIsCalled = true;
+ assert.sameValue(this.count, 3);
+ },
+ count: 0,
+ returnIsCalled: false,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done:false, value: 3};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(TypeError,
+ ()=> {lf.formatToParts(iterator_close_call_return)},
+ "Iterable yielded 3 which is not a string");
+assert.sameValue(iterator_close_call_return.count, 3);
+assert.sameValue(iterator_close_call_return.returnIsCalled, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorstep-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorstep-throw.js
new file mode 100644
index 0000000000..7edbce28da
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorstep-throw.js
@@ -0,0 +1,45 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts() while iteratorStep throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_step_throw = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ throw new CustomError();
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.formatToParts(iterator_step_throw)});
+assert.sameValue(iterator_step_throw.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorvalue-throw.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorvalue-throw.js
new file mode 100644
index 0000000000..000da967e1
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-iteratorvalue-throw.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts() while iteratorValue throws error.
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+features: [Intl.ListFormat]
+---*/
+
+function CustomError() {}
+
+let lf = new Intl.ListFormat();
+// Test the failure case.
+let iterator_value_throw = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++;
+ if (this.count == 3) {
+ return {done: false, get value() { throw new CustomError() }};
+ }
+ if (this.count < 5) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+assert.throws(CustomError,
+ ()=> {lf.formatToParts(iterator_value_throw)});
+assert.sameValue(iterator_value_throw.count, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-undefined.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-undefined.js
new file mode 100644
index 0000000000..ced482c74b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable-undefined.js
@@ -0,0 +1,21 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.format
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts(undefined).
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+features: [Intl.ListFormat]
+includes: [compareArray.js]
+---*/
+
+let lf = new Intl.ListFormat();
+
+assert.compareArray([], lf.formatToParts(undefined));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable.js
new file mode 100644
index 0000000000..053a30c2b9
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/iterable.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the behavior of Abstract Operation StringListFromIterable
+ called by Intl.ListFormat.prototype.formatToParts().
+info: |
+ StringListFromIterable
+ 1. If iterable is undefined, then
+ a. Return a new empty List.
+ 2. Let iteratorRecord be ? GetIterator(iterable).
+ 3. Let list be a new empty List.
+ 4. Let next be true.
+ 5. Repeat, while next is not false
+ a. Set next to ? IteratorStep(iteratorRecord).
+ b. If next is not false, then
+ i. Let nextValue be ? IteratorValue(next).
+ ii. If Type(nextValue) is not String, then
+ 1. Let error be ThrowCompletion(a newly created TypeError object).
+ 2. Return ? IteratorClose(iteratorRecord, error).
+ iii. Append nextValue to the end of the List list.
+ 6. Return list.
+features: [Intl.ListFormat]
+---*/
+
+let lf = new Intl.ListFormat();
+
+// Test the success case.
+let iterable_of_strings = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ count: 0,
+ next() {
+ this.count++
+ if (this.count < 4) {
+ return {done: false, value: String(this.count)};
+ }
+ return {done:true}
+ }
+};
+lf.formatToParts(iterable_of_strings);
+assert.sameValue(iterable_of_strings.count, 4);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/length.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/length.js
new file mode 100644
index 0000000000..fe1298ea4a
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the "length" property of Intl.ListFormat.prototype.formatToParts().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The ListFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.formatToParts, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/name.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/name.js
new file mode 100644
index 0000000000..f63e7662d4
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the "name" property of Intl.ListFormat.prototype.formatToParts().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.formatToParts, "name", {
+ value: "formatToParts",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/prop-desc.js
new file mode 100644
index 0000000000..4e94bd8a27
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/prop-desc.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.formatToParts
+description: >
+ Checks the "formatToParts" property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.formatToParts ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.ListFormat.prototype.formatToParts,
+ "function",
+ "typeof Intl.ListFormat.prototype.formatToParts is function"
+);
+
+verifyProperty(Intl.ListFormat.prototype, "formatToParts", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/formatToParts/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/prototype/prop-desc.js
new file mode 100644
index 0000000000..ee5b1cc868
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/prop-desc.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype
+description: >
+ Checks the "prototype" property of the ListFormat constructor.
+info: |
+ Intl.ListFormat.prototype
+
+ The value of Intl.ListFormat.prototype is %ListFormatPrototype%.
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/branding.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/branding.js
new file mode 100644
index 0000000000..bec62d5492
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/branding.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Verifies the branding check for the "resolvedOptions" function of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.resolvedOptions()
+
+ 2. If Type(pr) is not Object, throw a TypeError exception.
+ 3. If pr does not have an [[InitializedListFormat]] internal slot, throw a TypeError exception.
+features: [Intl.ListFormat]
+---*/
+
+const resolvedOptions = Intl.ListFormat.prototype.resolvedOptions;
+
+assert.sameValue(typeof resolvedOptions, "function");
+assert.throws(TypeError, () => resolvedOptions.call(undefined), "undefined");
+assert.throws(TypeError, () => resolvedOptions.call(null), "null");
+assert.throws(TypeError, () => resolvedOptions.call(true), "true");
+assert.throws(TypeError, () => resolvedOptions.call(""), "empty string");
+assert.throws(TypeError, () => resolvedOptions.call(Symbol()), "symbol");
+assert.throws(TypeError, () => resolvedOptions.call(1), "1");
+assert.throws(TypeError, () => resolvedOptions.call({}), "plain object");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.ListFormat), "Intl.ListFormat");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.ListFormat.prototype), "Intl.ListFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/caching.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/caching.js
new file mode 100644
index 0000000000..7570e6eb66
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/caching.js
@@ -0,0 +1,19 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Verifies that the return value of Intl.ListFormat.prototype.resolvedOptions() is not cached.
+info: |
+ Intl.ListFormat.prototype.resolvedOptions ()
+
+ 4. Let options be ! ObjectCreate(%ObjectPrototype%).
+features: [Intl.ListFormat]
+---*/
+
+const lf = new Intl.ListFormat("en-us");
+const options1 = lf.resolvedOptions();
+const options2 = lf.resolvedOptions();
+assert.notSameValue(options1, options2, "Should create a new object each time.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..e613f177d8
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/length.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Checks the "length" property of Intl.ListFormat.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The ListFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..22303e2d66
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/name.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Checks the "name" property of Intl.ListFormat.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..a1231a3696
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/order.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.ListFormat]
+---*/
+
+const options = new Intl.ListFormat().resolvedOptions();
+
+const expected = [
+ "locale",
+ "type",
+ "style",
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..59592c918d
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Checks the "resolvedOptions" property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype.resolvedOptions ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.ListFormat.prototype.resolvedOptions,
+ "function",
+ "typeof Intl.ListFormat.prototype.resolvedOptions is function"
+);
+
+verifyProperty(Intl.ListFormat.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/type.js b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/type.js
new file mode 100644
index 0000000000..e7ee1bd377
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/resolvedOptions/type.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.ListFormat.prototype.resolvedOptions
+description: Checks the properties of the result of Intl.ListFormat.prototype.resolvedOptions().
+info: |
+ Intl.ListFormat.prototype.resolvedOptions ()
+
+ 4. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 5. For each row of Table 1, except the header row, do
+ d. Perform ! CreateDataPropertyOrThrow(options, p, v).
+includes: [propertyHelper.js]
+features: [Intl.ListFormat]
+---*/
+
+const lf = new Intl.ListFormat("en-us", { "style": "short", "type": "unit" });
+const options = lf.resolvedOptions();
+assert.sameValue(Object.getPrototypeOf(options), Object.prototype, "Prototype");
+
+verifyProperty(options, "locale", {
+ value: "en-US",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "type", {
+ value: "unit",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "style", {
+ value: "short",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..ee0a3b462b
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toString.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.ListFormat.prototype-@@tostringtag
+description: >
+ Checks Object.prototype.toString with Intl.ListFormat objects.
+info: |
+ Intl.ListFormat.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.ListFormat".
+features: [Intl.ListFormat]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.ListFormat.prototype), "[object Intl.ListFormat]");
+assert.sameValue(Object.prototype.toString.call(new Intl.ListFormat()), "[object Intl.ListFormat]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..af1303f1c5
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.ListFormat.prototype-@@tostringtag
+description: >
+ Checks the @@toStringTag property of the ListFormat prototype object.
+info: |
+ Intl.ListFormat.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.ListFormat".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.ListFormat, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.ListFormat.prototype, Symbol.toStringTag, {
+ value: "Intl.ListFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/ListFormat/shell.js b/js/src/tests/test262/intl402/ListFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/ListFormat/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/browser.js b/js/src/tests/test262/intl402/Locale/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/canonicalize-locale-list-take-locale.js b/js/src/tests/test262/intl402/Locale/canonicalize-locale-list-take-locale.js
new file mode 100644
index 0000000000..1f20ba61fb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/canonicalize-locale-list-take-locale.js
@@ -0,0 +1,55 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies CanonicalizeLocaleList will take Intl.Locale as locales.
+info: |
+ CanonicalizeLocaleList ( locales )
+ 3. If Type(locales) is String or locales has an [[InitializedLocale]] internal slot, then
+ a. Let O be CreateArrayFromList(« locales »).
+
+ c. iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
+ 1. Let tag be kValue.[[Locale]].
+ iv. Else,
+ 1. Let tag be ? ToString(kValue).
+features: [Intl.Locale]
+---*/
+
+const tag = "ar";
+const tag2 = "fa";
+const tag3 = "zh";
+const loc = new Intl.Locale(tag);
+
+// Monkey-patching Intl.Locale
+class PatchedLocale extends Intl.Locale {
+ constructor(tag, options) {
+ super(tag, options);
+ }
+ toString() {
+ // this should NOT get called.
+ assert(false, "toString should not be called")
+ }
+}
+const ploc = new PatchedLocale(tag2);
+
+// Test Intl.Locale as the only argument
+let res = Intl.getCanonicalLocales(loc);
+assert.sameValue(res.length, 1);
+assert.sameValue(res[0], tag);
+
+// Test Monkey-patched Intl.Locale as the only argument
+res = Intl.getCanonicalLocales(ploc);
+assert.sameValue(res.length, 1);
+assert.sameValue(res[0], tag2);
+
+// Test Intl.Locale and the Monkey-patched one are in
+// array.
+res = Intl.getCanonicalLocales([loc, tag3, ploc]);
+assert.sameValue(res.length, 3);
+assert.sameValue(res[0], tag);
+assert.sameValue(res[1], tag3);
+assert.sameValue(res[2], tag2);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-apply-options-canonicalizes-twice.js b/js/src/tests/test262/intl402/Locale/constructor-apply-options-canonicalizes-twice.js
new file mode 100644
index 0000000000..e6d3f9a8b4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-apply-options-canonicalizes-twice.js
@@ -0,0 +1,28 @@
+// Copyright 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-apply-options-to-tag
+description: >
+ ApplyOptionsToTag canonicalises the language tag two times.
+info: |
+ 10.1.1 ApplyOptionsToTag( tag, options )
+
+ ...
+ 9. Set tag to CanonicalizeUnicodeLocaleId(tag).
+ 10. If language is not undefined,
+ ...
+ b. Set tag to tag with the substring corresponding to the unicode_language_subtag
+ production of the unicode_language_id replaced by the string language.
+ ...
+ 13. Return CanonicalizeUnicodeLocaleId(tag).
+features: [Intl.Locale]
+---*/
+
+// ApplyOptionsToTag canonicalises the locale identifier before applying the
+// options. That means "und-Armn-SU" is first canonicalised to "und-Armn-AM",
+// then the language is changed to "ru". If "ru" were applied first, the result
+// would be "ru-Armn-RU" instead.
+assert.sameValue(new Intl.Locale("und-Armn-SU", {language: "ru"}).toString(), "ru-Armn-AM");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-getter-order.js b/js/src/tests/test262/intl402/Locale/constructor-getter-order.js
new file mode 100644
index 0000000000..4948f22480
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-getter-order.js
@@ -0,0 +1,127 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the order of evaluations of arguments and options for the Locale
+ constructor.
+features: [Intl.Locale]
+includes: [compareArray.js]
+---*/
+
+const order = [];
+new Intl.Locale(
+ { toString() { order.push("tag toString"); return "en"; } },
+ {
+ get language() {
+ order.push("get language");
+ return {
+ toString() {
+ order.push("toString language");
+ return "de";
+ }
+ }
+ },
+
+ get script() {
+ order.push("get script");
+ return {
+ toString() {
+ order.push("toString script");
+ return "Latn";
+ }
+ }
+ },
+
+ get region() {
+ order.push("get region");
+ return {
+ toString() {
+ order.push("toString region");
+ return "DE";
+ }
+ }
+ },
+
+ get calendar() {
+ order.push("get calendar");
+ return {
+ toString() {
+ order.push("toString calendar");
+ return "gregory";
+ }
+ }
+ },
+
+ get collation() {
+ order.push("get collation");
+ return {
+ toString() {
+ order.push("toString collation");
+ return "zhuyin";
+ }
+ }
+ },
+
+ get hourCycle() {
+ order.push("get hourCycle");
+ return {
+ toString() {
+ order.push("toString hourCycle");
+ return "h24";
+ }
+ }
+ },
+
+ get caseFirst() {
+ order.push("get caseFirst");
+ return {
+ toString() {
+ order.push("toString caseFirst");
+ return "upper";
+ }
+ }
+ },
+
+ get numeric() {
+ order.push("get numeric");
+ return false;
+ },
+
+ get numberingSystem() {
+ order.push("get numberingSystem");
+ return {
+ toString() {
+ order.push("toString numberingSystem");
+ return "latn";
+ }
+ }
+ },
+ }
+);
+
+const expected_order = [
+ "tag toString",
+ "get language",
+ "toString language",
+ "get script",
+ "toString script",
+ "get region",
+ "toString region",
+ "get calendar",
+ "toString calendar",
+ "get collation",
+ "toString collation",
+ "get hourCycle",
+ "toString hourCycle",
+ "get caseFirst",
+ "toString caseFirst",
+ "get numeric",
+ "get numberingSystem",
+ "toString numberingSystem"
+];
+
+assert.compareArray(order, expected_order);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-locale-object.js b/js/src/tests/test262/intl402/Locale/constructor-locale-object.js
new file mode 100644
index 0000000000..11760b13c8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-locale-object.js
@@ -0,0 +1,39 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 10. Return CanonicalizeLanguageTag(tag).
+features: [Intl.Locale]
+---*/
+
+// Pass Intl.Locale object and replace subtag.
+const enUS = new Intl.Locale("en-US");
+const enGB = new Intl.Locale(enUS, {region: "GB"});
+
+assert.sameValue(enUS.toString(), "en-US", 'enUS.toString() returns "en-US"');
+assert.sameValue(enGB.toString(), "en-GB", 'enGB.toString() returns "en-GB"');
+
+// Pass Intl.Locale object and replace Unicode extension keyword.
+const zhUnihan = new Intl.Locale("zh-u-co-unihan");
+const zhZhuyin = new Intl.Locale(zhUnihan, {collation: "zhuyin"});
+
+assert.sameValue(
+ zhUnihan.toString(),
+ "zh-u-co-unihan",
+ 'zhUnihan.toString() returns "zh-u-co-unihan"'
+);
+assert.sameValue(
+ zhZhuyin.toString(),
+ "zh-u-co-zhuyin",
+ 'zhZhuyin.toString() returns "zh-u-co-zhuyin"'
+);
+
+assert.sameValue(zhUnihan.collation, "unihan", 'The value of zhUnihan.collation is "unihan"');
+assert.sameValue(zhZhuyin.collation, "zhuyin", 'The value of zhZhuyin.collation is "zhuyin"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-newtarget-undefined.js b/js/src/tests/test262/intl402/Locale/constructor-newtarget-undefined.js
new file mode 100644
index 0000000000..c63fc1c044
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-newtarget-undefined.js
@@ -0,0 +1,25 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the NewTarget check for Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ Intl.Locale();
+}, 'Intl.Locale() throws TypeError');
+
+assert.throws(TypeError, function() {
+ Intl.Locale("en");
+}, 'Intl.Locale("en") throws TypeError');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-non-iana-canon.js b/js/src/tests/test262/intl402/Locale/constructor-non-iana-canon.js
new file mode 100644
index 0000000000..14191e3014
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-non-iana-canon.js
@@ -0,0 +1,112 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization, minimization and maximization of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 10. Return CanonicalizeLanguageTag(tag).
+
+ Intl.Locale.prototype.maximize ()
+ 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]].
+
+ Intl.Locale.prototype.minimize ()
+ 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
+features: [Intl.Locale]
+---*/
+
+// Test some language tags where we know that either CLDR or ICU produce
+// different results compared to the canonicalization specified in RFC 5646.
+var testData = [
+ {
+ tag: "mo",
+ canonical: "ro",
+ maximized: "ro-Latn-RO",
+ },
+ {
+ tag: "es-ES-preeuro",
+ maximized: "es-Latn-ES-preeuro",
+ minimized: "es-preeuro",
+ },
+ {
+ tag: "uz-UZ-cyrillic",
+ maximized: "uz-Latn-UZ-cyrillic",
+ minimized: "uz-cyrillic",
+ },
+ {
+ tag: "posix",
+ },
+ {
+ tag: "hi-direct",
+ maximized: "hi-Deva-IN-direct",
+ },
+ {
+ tag: "zh-pinyin",
+ maximized: "zh-Hans-CN-pinyin",
+ },
+ {
+ tag: "zh-stroke",
+ maximized: "zh-Hans-CN-stroke",
+ },
+ {
+ tag: "aar-x-private",
+ // "aar" should be canonicalized into "aa" because "aar" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "aa-x-private",
+ maximized: "aa-Latn-ET-x-private",
+ },
+ {
+ tag: "heb-x-private",
+ // "heb" should be canonicalized into "he" because "heb" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "he-x-private",
+ maximized: "he-Hebr-IL-x-private",
+ },
+ {
+ tag: "de-u-kf",
+ maximized: "de-Latn-DE-u-kf",
+ },
+ {
+ tag: "ces",
+ // "ces" should be canonicalized into "cs" because "ces" matches the type attribute of
+ // a languageAlias element in
+ // https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
+ canonical: "cs",
+ maximized: "cs-Latn-CZ",
+ },
+ {
+ tag: "hy-arevela",
+ canonical: "hy",
+ maximized: "hy-Armn-AM",
+ },
+ {
+ tag: "hy-arevmda",
+ canonical: "hyw",
+ maximized: "hyw-Armn-AM",
+ },
+];
+
+for (const {tag, canonical = tag, maximized = canonical, minimized = canonical} of testData) {
+ const loc = new Intl.Locale(tag);
+ assert.sameValue(
+ new Intl.Locale(tag).toString(),
+ canonical,
+ `new Intl.Locale("${tag}").toString() returns "${canonical}"`
+ );
+ assert.sameValue(
+ new Intl.Locale(tag).maximize().toString(),
+ maximized,
+ `new Intl.Locale("${tag}").maximize().toString() returns "${maximized}"`
+ );
+ assert.sameValue(
+ new Intl.Locale(tag).minimize().toString(),
+ minimized,
+ `new Intl.Locale("${tag}").minimize().toString() returns "${minimized}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-calendar-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-calendar-invalid.js
new file mode 100644
index 0000000000..0ac2861b99
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-calendar-invalid.js
@@ -0,0 +1,38 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 15. If calendar is not undefined, then
+ a. If calendar does not match the [(3*8alphanum) *("-" (3*8alphanum))] sequence, throw a RangeError exception.
+ 16. Set opt.[[ca]] to calendar.
+
+features: [Intl.Locale]
+---*/
+
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ calendar = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidCalendarOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+];
+for (const calendar of invalidCalendarOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {calendar});
+ }, `new Intl.Locale("en", {calendar: "${calendar}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-calendar-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-calendar-valid.js
new file mode 100644
index 0000000000..4a1049bf7a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-calendar-valid.js
@@ -0,0 +1,43 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 14. Let calendar be ? GetOption(options, "calendar", "string", undefined, undefined).
+ ...
+
+features: [Intl.Locale]
+---*/
+
+const validCalendarOptions = [
+ ["abc", "en-u-ca-abc"],
+ ["abcd", "en-u-ca-abcd"],
+ ["abcde", "en-u-ca-abcde"],
+ ["abcdef", "en-u-ca-abcdef"],
+ ["abcdefg", "en-u-ca-abcdefg"],
+ ["abcdefgh", "en-u-ca-abcdefgh"],
+ ["12345678", "en-u-ca-12345678"],
+ ["1234abcd", "en-u-ca-1234abcd"],
+ ["1234abcd-abc123", "en-u-ca-1234abcd-abc123"],
+];
+for (const [calendar, expected] of validCalendarOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', { calendar }).toString(),
+ expected,
+ `new Intl.Locale('en', { calendar: "${calendar}" }).toString() returns "${expected}"`
+ );
+ assert.sameValue(
+ new Intl.Locale('en-u-ca-gregory', { calendar }).toString(),
+ expected,
+ `new Intl.Locale('en-u-ca-gregory', { calendar: "${calendar}" }).toString() returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-canonicalized.js b/js/src/tests/test262/intl402/Locale/constructor-options-canonicalized.js
new file mode 100644
index 0000000000..6019635f15
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-canonicalized.js
@@ -0,0 +1,71 @@
+// Copyright 2020 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-resolvelocale
+description: >
+ Values provided as properties of the options-argument to the Locale
+ constructor are converted to canonical form.
+info: |
+ ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData )
+
+ ...
+ 9.i.iii.1. Let optionsValue be the string optionsValue after performing the algorithm steps to transform Unicode extension values to canonical syntax per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
+ 9.i.iii.2. Let optionsValue be the string optionsValue after performing the algorithm steps to replace Unicode extension values with their canonical form per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
+ ...
+
+features: [Intl.Locale]
+---*/
+
+const keyValueTests = [
+ {
+ key: "ca",
+ option: "calendar",
+ tests: [
+ ["islamicc", "islamic-civil"],
+ ["ethiopic-amete-alem", "ethioaa"],
+ ],
+ },
+];
+
+for (const { key, option, tests } of keyValueTests) {
+ for (const [noncanonical, canonical] of tests) {
+ let canonicalInLocale =
+ new Intl.Locale(`en-u-${key}-${canonical}`);
+
+ assert.sameValue(
+ canonicalInLocale[option],
+ canonical,
+ `new Intl.Locale("en-u-${key}-${canonical}").${option} returns ${canonical}`
+ );
+
+ let canonicalInOption =
+ new Intl.Locale(`en`, { [option]: canonical });
+
+ assert.sameValue(
+ canonicalInOption[option],
+ canonical,
+ `new Intl.Locale("en", { ${option}: "${canonical}" }).${option} returns ${canonical}`
+ );
+
+ let noncanonicalInLocale =
+ new Intl.Locale(`en-u-${key}-${noncanonical}`);
+
+ assert.sameValue(
+ noncanonicalInLocale[option],
+ canonical,
+ `new Intl.Locale("en-u-${key}-${noncanonical}").${option} returns ${canonical}`
+ );
+
+ let noncanonicalInOption =
+ new Intl.Locale(`en`, { [option]: noncanonical });
+
+ assert.sameValue(
+ noncanonicalInOption[option],
+ canonical,
+ `new Intl.Locale("en", { ${option}: "${noncanonical}" }).${option} returns ${canonical}`
+ );
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-invalid.js
new file mode 100644
index 0000000000..e15c14c45c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-invalid.js
@@ -0,0 +1,39 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 22. Let kf be ? GetOption(options, "caseFirst", "string", « "upper", "lower", "false" », undefined).
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+ ...
+ 2. d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.Locale]
+---*/
+
+
+const invalidCaseFirstOptions = [
+ "",
+ "u",
+ "Upper",
+ "upper\0",
+ "uppercase",
+ "true",
+ { valueOf() { return false; } },
+];
+for (const caseFirst of invalidCaseFirstOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {caseFirst});
+ }, `new Intl.Locale("en", {caseFirst: "${caseFirst}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-valid.js
new file mode 100644
index 0000000000..fc5b837c64
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-casefirst-valid.js
@@ -0,0 +1,68 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 22. Let kf be ? GetOption(options, "caseFirst", "string", « "upper", "lower", "false" », undefined).
+ 23. Set opt.[[kf]] to kf.
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const validCaseFirstOptions = [
+ "upper",
+ "lower",
+ "false",
+ false,
+ { toString() { return false; } },
+];
+for (const caseFirst of validCaseFirstOptions) {
+ const expected = String(caseFirst);
+ let expect = "en-u-kf-" + expected;
+ assert.sameValue(
+ new Intl.Locale('en', { caseFirst }).toString(),
+ expect,
+ `new Intl.Locale("en", { caseFirst: "${caseFirst}" }).toString() returns "${expect}"`
+ );
+
+ expect = "en-u-kf-" + expected;
+ assert.sameValue(
+ new Intl.Locale('en-u-kf-lower', { caseFirst }).toString(),
+ expect,
+ `new Intl.Locale("en-u-kf-lower", { caseFirst: "${caseFirst}" }).toString() returns "${expect}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-kf-lower', { caseFirst }).caseFirst,
+ expected,
+ `new Intl.Locale("en-u-kf-lower", { caseFirst }).caseFirst equals "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-collation-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-collation-invalid.js
new file mode 100644
index 0000000000..d256093257
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-collation-invalid.js
@@ -0,0 +1,36 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 18. If collation is not undefined, then
+ a. If collation does not match the [(3*8alphanum) *("-" (3*8alphanum))] sequence, throw a RangeError exception.
+
+features: [Intl.Locale]
+---*/
+
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ collation = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidCollationOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+];
+for (const invalidCollationOption of invalidCollationOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {collation: invalidCollationOption});
+ }, '`new Intl.Locale("en", {collation: invalidCollationOption})` throws RangeError');
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-collation-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-collation-valid.js
new file mode 100644
index 0000000000..e9bf3077a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-collation-valid.js
@@ -0,0 +1,64 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 17. Let collation be ? GetOption(options, "collation", "string", undefined, undefined).
+ ...
+ 19. Set opt.[[co]] to collation.
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const validCollationOptions = [
+ ["abc", "en-u-co-abc"],
+ ["abcd", "en-u-co-abcd"],
+ ["abcde", "en-u-co-abcde"],
+ ["abcdef", "en-u-co-abcdef"],
+ ["abcdefg", "en-u-co-abcdefg"],
+ ["abcdefgh", "en-u-co-abcdefgh"],
+ ["12345678", "en-u-co-12345678"],
+ ["1234abcd", "en-u-co-1234abcd"],
+ ["1234abcd-abc123", "en-u-co-1234abcd-abc123"],
+];
+for (const [collation, expected] of validCollationOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', {collation}).toString(),
+ expected,
+ `new Intl.Locale('en', {collation: "${collation}"}).toString() returns "${expected}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-co-gregory', {collation}).toString(),
+ expected,
+ `new Intl.Locale('en-u-co-gregory', {collation: "${collation}"}).toString() returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-invalid.js
new file mode 100644
index 0000000000..5d03d0755c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-invalid.js
@@ -0,0 +1,34 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ x. Let numberingSystem be ? GetOption(options, "firstDayOfWeek", "string", < *"mon"*, *"tue"*, *"wed"*, *"thu"*, *"fri"*, *"sat"*, *"sun"*, *"0"*, *"1"*, *"2"*, *"3"*, *"4"*, *"5"*, *"6"*, *"7"*> , undefined).
+ ...
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const invalidFirstDayOfWeekOptions = [
+ "",
+ "m",
+ "mo",
+ "monday",
+ true,
+ false,
+ null,
+];
+for (const firstDayOfWeek of invalidFirstDayOfWeekOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale('en', {firstDayOfWeek});
+ }, `new Intl.Locale("en", {firstDayOfWeek: "${firstDayOfWeek}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-valid.js
new file mode 100644
index 0000000000..0c5a84cc3a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-firstDayOfWeek-valid.js
@@ -0,0 +1,68 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ x. Let numberingSystem be ? GetOption(options, "firstDayOfWeek", "string", < *"mon"*, *"tue"*, *"wed"*, *"thu"*, *"fri"*, *"sat"*, *"sun"*, *"0"*, *"1"*, *"2"*, *"3"*, *"4"*, *"5"*, *"6"*, *"7"*> , undefined).
+ x. Let firstDay be *undefined*.
+ x. If fw is not *undefined*, then
+ x. Set firstDay to !WeekdayToString(fw).
+ x. Set opt.[[fw]] to firstDay.
+ ...
+ x. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+ x. Let firstDay be *undefined*.
+ x. If r.[[fw]] is not *undefined*, then
+ x. Set firstDay to ! WeekdayToNumber(r.[[fw]]).
+ x. Set locale.[[FirstDayOfWeek]] to firstDay.
+ ...
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const validFirstDayOfWeekOptions = [
+ ["mon", "en-u-fw-mon"],
+ ["tue", "en-u-fw-tue"],
+ ["wed", "en-u-fw-wed"],
+ ["thu", "en-u-fw-thu"],
+ ["fri", "en-u-fw-fri"],
+ ["sat", "en-u-fw-sat"],
+ ["sun", "en-u-fw-sun"],
+ ["1", "en-u-fw-mon"],
+ ["2", "en-u-fw-tue"],
+ ["3", "en-u-fw-wed"],
+ ["4", "en-u-fw-thu"],
+ ["5", "en-u-fw-fri"],
+ ["6", "en-u-fw-sat"],
+ ["7", "en-u-fw-sun"],
+ ["0", "en-u-fw-sun"],
+ [1, "en-u-fw-mon"],
+ [2, "en-u-fw-tue"],
+ [3, "en-u-fw-wed"],
+ [4, "en-u-fw-thu"],
+ [5, "en-u-fw-fri"],
+ [6, "en-u-fw-sat"],
+ [7, "en-u-fw-sun"],
+ [0, "en-u-fw-sun"],
+];
+for (const [firstDayOfWeek, expected] of validFirstDayOfWeekOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', { firstDayOfWeek }).toString(),
+ expected,
+ `new Intl.Locale("en", { firstDayOfWeek: ${firstDayOfWeek} }).toString() returns "${expected}"`
+ );
+ assert.sameValue(
+ new Intl.Locale('en-u-fw-WED', { firstDayOfWeek }).toString(),
+ expected,
+ `new Intl.Locale("en-u-fw-WED", { firstDayOfWeek: ${firstDayOfWeek} }).toString() returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-invalid.js
new file mode 100644
index 0000000000..a266bbb7b1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-invalid.js
@@ -0,0 +1,45 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 20. Let hc be ? GetOption(options, "hourCycle", "string", « "h11", "h12", "h23", "h24" », undefined).
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+ ...
+ 2. d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ ...
+features: [Intl.Locale]
+---*/
+
+
+const invalidHourCycleOptions = [
+ "",
+ "h",
+ "h00",
+ "h01",
+ "h10",
+ "h13",
+ "h22",
+ "h25",
+ "h48",
+ "h012",
+ "h120",
+ "h12\0",
+ "H12",
+];
+for (const hourCycle of invalidHourCycleOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {hourCycle});
+ }, `new Intl.Locale("en", {hourCycle: "${hourCycle}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-valid.js
new file mode 100644
index 0000000000..f314ce23ff
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-hourcycle-valid.js
@@ -0,0 +1,74 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 20. Let hc be ? GetOption(options, "hourCycle", "string", « "h11", "h12", "h23", "h24" », undefined).
+ 21. Set opt.[[hc]] to hc.
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const validHourCycleOptions = [
+ 'h11',
+ 'h12',
+ 'h23',
+ 'h24',
+ { toString() { return 'h24'; } },
+];
+for (const hourCycle of validHourCycleOptions) {
+ const expected = String(hourCycle);
+ let expect = 'en-u-hc-' + expected;
+
+ assert.sameValue(
+ new Intl.Locale('en', {hourCycle}).toString(),
+ expect,
+ `new Intl.Locale("en", {hourCycle: "${hourCycle}"}).toString() returns "${expect}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-hc-h00', {hourCycle}).toString(),
+ expect,
+ `new Intl.Locale("en-u-hc-h00", {hourCycle: "${hourCycle}"}).toString() returns "${expect}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-hc-h12', {hourCycle}).toString(),
+ expect,
+ `new Intl.Locale("en-u-hc-h12", {hourCycle: "${hourCycle}"}).toString() returns "${expect}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-hc-h00', {hourCycle}).hourCycle,
+ expected,
+ `new Intl.Locale("en-u-hc-h00", {hourCycle: "${hourCycle}"}).hourCycle equals "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-language-grandfathered.js b/js/src/tests/test262/intl402/Locale/constructor-options-language-grandfathered.js
new file mode 100644
index 0000000000..0d73349aa2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-language-grandfathered.js
@@ -0,0 +1,35 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ ApplyOptionsToTag( tag, options )
+ ...
+ 3. Let language be ? GetOption(options, "language", "string", undefined, undefined).
+ 4. If language is not undefined, then
+ a. If language does not match the language production, throw a RangeError exception.
+ b. If language matches the grandfathered production, throw a RangeError exception.
+ ...
+
+features: [Intl.Locale]
+---*/
+
+assert.throws(RangeError, function() {
+ new Intl.Locale("nb", {
+ language: "no-bok",
+ });
+}, `new Intl.Locale("nb", {language: "no-bok"}) throws RangeError`);
+
+assert.throws(RangeError, function() {
+ new Intl.Locale("nb", {
+ language: "no-bok",
+ region: "NO",
+ });
+}, `new Intl.Locale("nb", {language: "no-bok", region: "NO"}) throws RangeError`);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-language-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-language-invalid.js
new file mode 100644
index 0000000000..512cf1d67e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-language-invalid.js
@@ -0,0 +1,71 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 4. If language is not undefined, then
+ a. If language does not match the language production, throw a RangeError exception.
+ ...
+
+features: [Intl.Locale]
+---*/
+
+/*
+ language = 2*3ALPHA ; shortest ISO 639 code
+ ["-" extlang] ; sometimes followed by
+ ; extended language subtags
+ / 4ALPHA ; or reserved for future use
+ / 5*8ALPHA ; or registered language subtag
+
+ extlang = 3ALPHA ; selected ISO 639 codes
+ *2("-" 3ALPHA) ; permanently reserved
+*/
+const invalidLanguageOptions = [
+ "",
+ "a",
+ "ab7",
+ "notalanguage",
+ "undefined",
+
+ // Value contains more than just the 'language' production.
+ "fr-Latn",
+ "fr-FR",
+ "sa-vaidika",
+ "fr-a-asdf",
+ "fr-x-private",
+
+ // Irregular grandfathered language tag.
+ "i-klingon",
+
+ // Regular grandfathered language tag.
+ "zh-min",
+ "zh-min-nan",
+
+ // Reserved with extended language subtag
+ "abcd-US",
+ "abcde-US",
+ "abcdef-US",
+ "abcdefg-US",
+ "abcdefgh-US",
+
+ 7,
+];
+for (const language of invalidLanguageOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {language});
+ }, `new Intl.Locale("en", {language: "${language}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-language-valid-undefined.js b/js/src/tests/test262/intl402/Locale/constructor-options-language-valid-undefined.js
new file mode 100644
index 0000000000..c1ef7ef355
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-language-valid-undefined.js
@@ -0,0 +1,45 @@
+// Copyright 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verify valid language option values (undefined)
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ ...
+
+ IsStructurallyValidLanguageTag ( locale )
+
+ The IsStructurallyValidLanguageTag abstract operation verifies that the
+ locale argument (which must be a String value)
+
+ represents a well-formed Unicode BCP 47 Locale Identifier" as specified in
+ Unicode Technical Standard 35 section 3.2, or successor,
+
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ new Intl.Locale('en', {language: undefined}).toString(),
+ 'en',
+ `new Intl.Locale('en', {language: undefined}).toString() returns "en"`
+);
+
+assert.sameValue(
+ new Intl.Locale('en-US', {language: undefined}).toString(),
+ 'en-US',
+ `new Intl.Locale('en-US', {language: undefined}).toString() returns "en-US"`
+);
+
+assert.throws(RangeError, () => new Intl.Locale('en-els', {language: undefined}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-language-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-language-valid.js
new file mode 100644
index 0000000000..0f1f0b1e32
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-language-valid.js
@@ -0,0 +1,68 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verify valid language option values (various)
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ 3. Let language be ? GetOption(options, "language", "string", undefined, undefined).
+ 4. If language is not undefined, then
+ a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
+
+ IsStructurallyValidLanguageTag ( locale )
+
+ The IsStructurallyValidLanguageTag abstract operation verifies that the
+ locale argument (which must be a String value)
+
+ represents a well-formed Unicode BCP 47 Locale Identifier" as specified in
+ Unicode Technical Standard 35 section 3.2, or successor,
+
+features: [Intl.Locale]
+---*/
+
+const validLanguageOptions = [
+ [{ toString() { return 'de' } }, 'de'],
+];
+for (const [language, expected] of validLanguageOptions) {
+ let expect = expected || 'en';
+
+ assert.sameValue(
+ new Intl.Locale('en', {language}).toString(),
+ expect,
+ `new Intl.Locale('en', {language: "${language}"}).toString() returns "${expect}"`
+ );
+
+ expect = (expected || 'en') + '-US';
+ assert.sameValue(
+ new Intl.Locale('en-US', {language}).toString(),
+ expect,
+ `new Intl.Locale('en-US', {language: "${language}"}).toString() returns "${expect}"`
+ );
+
+ assert.throws(RangeError, () => new Intl.Locale('en-els', {language}));
+
+}
+
+const invalidLanguageOptions = [
+ null,
+ 'zh-cmn',
+ 'ZH-CMN',
+ 'abcd',
+];
+for (const language of invalidLanguageOptions) {
+ assert.throws(RangeError, () => new Intl.Locale('en', {language}));
+ assert.throws(RangeError, () => new Intl.Locale('en-US', {language}));
+ assert.throws(RangeError, () => new Intl.Locale('en-els', {language}));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-invalid.js
new file mode 100644
index 0000000000..e3e147447d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-invalid.js
@@ -0,0 +1,43 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 28. If numberingSystem is not undefined, then
+ a. If numberingSystem does not match the [(3*8alphanum) *("-" (3*8alphanum))] sequence, throw a RangeError exception.
+
+features: [Intl.Locale]
+---*/
+
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ numberingSystem = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidNumberingSystemOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-latn-",
+ "latn-",
+ "latn--",
+ "latn-ca",
+ "latn-ca-",
+ "latn-ca-gregory",
+];
+for (const numberingSystem of invalidNumberingSystemOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale('en', {numberingSystem});
+ }, `new Intl.Locale("en", {numberingSystem: "${numberingSystem}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-valid.js
new file mode 100644
index 0000000000..6b9cef0c29
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-numberingsystem-valid.js
@@ -0,0 +1,63 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 27. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
+ ...
+ 29. Set opt.[[nu]] to numberingSystem.
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const validNumberingSystemOptions = [
+ ["abc", "en-u-nu-abc"],
+ ["abcd", "en-u-nu-abcd"],
+ ["abcde", "en-u-nu-abcde"],
+ ["abcdef", "en-u-nu-abcdef"],
+ ["abcdefg", "en-u-nu-abcdefg"],
+ ["abcdefgh", "en-u-nu-abcdefgh"],
+ ["12345678", "en-u-nu-12345678"],
+ ["1234abcd", "en-u-nu-1234abcd"],
+ ["1234abcd-abc123", "en-u-nu-1234abcd-abc123"],
+];
+for (const [numberingSystem, expected] of validNumberingSystemOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', { numberingSystem }).toString(),
+ expected,
+ `new Intl.Locale("en", { numberingSystem: ${numberingSystem} }).toString() returns "${expected}"`
+ );
+ assert.sameValue(
+ new Intl.Locale('en-u-nu-latn', { numberingSystem }).toString(),
+ expected,
+ `new Intl.Locale("en-u-nu-latn", { numberingSystem: ${numberingSystem} }).toString() returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-numeric-undefined.js b/js/src/tests/test262/intl402/Locale/constructor-options-numeric-undefined.js
new file mode 100644
index 0000000000..d51f2513e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-numeric-undefined.js
@@ -0,0 +1,56 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: Verifies the behavior of an undefined numeric option to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 24. Let kn be ? GetOption(options, "numeric", "boolean", undefined, undefined).
+ 25. If kn is not undefined, set kn to ! ToString(kn).
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const options = { numeric: undefined };
+assert.sameValue(
+ new Intl.Locale('en', options).toString(),
+ "en",
+ 'new Intl.Locale("en", {numeric: undefined}).toString() returns "en"'
+);
+
+assert.sameValue(
+ new Intl.Locale('en-u-kn-true', options).toString(),
+ "en-u-kn",
+ 'new Intl.Locale("en-u-kn-true", {numeric: undefined}).toString() returns "en-u-kn"'
+);
+
+assert.sameValue(
+ new Intl.Locale('en-u-kf-lower', options).numeric,
+ false,
+ 'The value of new Intl.Locale("en-u-kf-lower", {numeric: undefined}).numeric equals `false`'
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-numeric-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-numeric-valid.js
new file mode 100644
index 0000000000..eaa1351961
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-numeric-valid.js
@@ -0,0 +1,70 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 24. Let kn be ? GetOption(options, "numeric", "boolean", undefined, undefined).
+ 25. If kn is not undefined, set kn to ! ToString(kn).
+ ...
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+ ...
+
+ ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys )
+
+ ...
+ 8. Let locale be the String value that is tag with all Unicode locale extension sequences removed.
+ 9. Let newExtension be ! CanonicalizeUnicodeExtension(attributes, keywords).
+ 10. If newExtension is not the empty String, then
+ a. Let locale be ! InsertUnicodeExtension(locale, newExtension).
+ ...
+
+ CanonicalizeUnicodeExtension( attributes, keywords )
+ ...
+ 4. Repeat for each element entry of keywords in List order,
+ a. Let keyword be entry.[[Key]].
+ b. If entry.[[Value]] is not the empty String, then
+ i. Let keyword be the string-concatenation of keyword, "-", and entry.[[Value]].
+ c. Append keyword to fullKeywords.
+ ...
+features: [Intl.Locale]
+---*/
+
+const validNumericOptions = [
+ [false, false],
+ [true, true],
+ [null, false],
+ [0, false],
+ [0.5, true],
+ ["true", true],
+ ["false", true],
+ [{ valueOf() { return false; } }, true],
+];
+for (const [numeric, expected] of validNumericOptions) {
+ let expect = expected ? "en-u-kn" : "en-u-kn-false";
+
+ assert.sameValue(
+ new Intl.Locale('en', {numeric}).toString(),
+ expect,
+ `new Intl.Locale("en", {numeric: ${numeric}}).toString() returns "${expected}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-kn-true', {numeric}).toString(),
+ expect,
+ `new Intl.Locale("en-u-kn-true", {numeric: ${numeric}}).toString() returns "${expected}"`
+ );
+
+ assert.sameValue(
+ new Intl.Locale('en-u-kf-lower', {numeric}).numeric,
+ expected,
+ `new Intl.Locale("en-u-kf-lower", {numeric: ${numeric}}).numeric equals "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-region-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-region-invalid.js
new file mode 100644
index 0000000000..5f5444b6b5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-region-invalid.js
@@ -0,0 +1,58 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 8. If region is not undefined, then
+ a. If region does not match the region production, throw a RangeError exception.
+ ...
+
+features: [Intl.Locale]
+---*/
+
+/*
+ region = 2ALPHA ; ISO 3166-1 code
+ / 3DIGIT ; UN M.49 code
+*/
+const invalidRegionOptions = [
+ "",
+ "a",
+ "abc",
+ "a7",
+
+ // Value cannot be parsed as a 'region' production.
+ "notaregion",
+
+ // Value contains more than just the 'region' production.
+ "SA-vaidika",
+ "SA-a-asdf",
+ "SA-x-private",
+
+ // Value contains more than just the 'script' production.
+ "ary-Arab",
+ "Latn-SA",
+ "Latn-vaidika",
+ "Latn-a-asdf",
+ "Latn-x-private",
+
+ 7,
+];
+for (const region of invalidRegionOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {region});
+ }, `new Intl.Locale("en", {region: "${region}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-region-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-region-valid.js
new file mode 100644
index 0000000000..f57fec2226
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-region-valid.js
@@ -0,0 +1,69 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 7. Let region be ? GetOption(options, "region", "string", undefined, undefined).
+ ...
+ 9. If tag matches neither the privateuse nor the grandfathered production, then
+ ...
+ d. If region is not undefined, then
+ i. If tag does not contain a region production, then
+ 1. Set tag to the concatenation of the language production of tag, the substring corresponding to the "-" script production if present, "-", region, and the rest of tag.
+ ii. Else,
+ 1. Set tag to tag with the substring corresponding to the region production replaced by the string region.
+
+features: [Intl.Locale]
+---*/
+
+const validRegionOptions = [
+ [undefined, undefined],
+ ['FR', 'en-FR'],
+ ['554', 'en-NZ'],
+ [554, 'en-NZ'],
+];
+for (const [region, expected] of validRegionOptions) {
+ let options = { region };
+ let expect = expected || 'en';
+
+ assert.sameValue(
+ new Intl.Locale('en', options).toString(),
+ expect,
+ `new Intl.Locale('en', {region: "${region}"}).toString() returns "${expect}"`
+ );
+
+ expect = expected || 'en-US';
+ assert.sameValue(
+ new Intl.Locale('en-US', options).toString(),
+ expect,
+ `new Intl.Locale('en-US', {region: "${region}"}).toString() returns "${expect}"`
+ );
+
+ expect = (expected || 'en') + '-u-ca-gregory';
+ assert.sameValue(
+ new Intl.Locale('en-u-ca-gregory', options).toString(),
+ expect,
+ `new Intl.Locale('en-u-ca-gregory', {region: "${region}"}).toString() returns "${expect}"`
+ );
+
+ expect = (expected || 'en-US') + '-u-ca-gregory';
+ assert.sameValue(
+ new Intl.Locale('en-US-u-ca-gregory', options).toString(),
+ expect,
+ `new Intl.Locale('en-US-u-ca-gregory', {region: "${region}"}).toString() returns "${expect}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-script-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-options-script-invalid.js
new file mode 100644
index 0000000000..01141210d5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-script-invalid.js
@@ -0,0 +1,54 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 6. If script is not undefined, then
+ a. If script does not match the script production, throw a RangeError exception.
+ ...
+
+features: [Intl.Locale]
+---*/
+
+/*
+ script = 4ALPHA ; ISO 15924 code
+*/
+const invalidScriptOptions = [
+ "",
+ "a",
+ "ab",
+ "abc",
+ "abc7",
+ "notascript",
+ "undefined",
+ "Bal\u0130",
+ "Bal\u0131",
+
+ // Value contains more than just the 'script' production.
+ "ary-Arab",
+ "Latn-SA",
+ "Latn-vaidika",
+ "Latn-a-asdf",
+ "Latn-x-private",
+
+ 7,
+];
+for (const script of invalidScriptOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale("en", {script});
+ }, `new Intl.Locale("en", {script: "${script}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-script-valid-undefined.js b/js/src/tests/test262/intl402/Locale/constructor-options-script-valid-undefined.js
new file mode 100644
index 0000000000..470d865d26
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-script-valid-undefined.js
@@ -0,0 +1,50 @@
+// Copyright 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verify valid script option values (undefined)
+info: |
+ Intl.Locale( tag [, options] )
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
+ ...
+ 9. If tag matches neither the privateuse nor the grandfathered production, then
+ ...
+ c. If script is not undefined, then
+ i. If tag does not contain a script production, then
+ 1. Set tag to the concatenation of the language production of tag, "-", script, and the rest of tag.
+ ii. Else,
+ 1. Set tag to tag with the substring corresponding to the script production replaced by the string script.
+
+
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ new Intl.Locale('en', {script: undefined}).toString(),
+ 'en',
+ `new Intl.Locale('en', {script: undefined}).toString() returns "en"`
+);
+
+assert.sameValue(
+ new Intl.Locale('en-DK', {script: undefined}).toString(),
+ 'en-DK',
+ `new Intl.Locale('en-DK', {script: undefined}).toString() returns "en-DK"`
+);
+
+assert.sameValue(
+ new Intl.Locale('en-Cyrl', {script: undefined}).toString(),
+ 'en-Cyrl',
+ `new Intl.Locale('en-Cyrl', {script: undefined}).toString() returns "en-Cyrl"`
+);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-script-valid.js b/js/src/tests/test262/intl402/Locale/constructor-options-script-valid.js
new file mode 100644
index 0000000000..43fb26ff62
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-script-valid.js
@@ -0,0 +1,64 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verify valid language option values (various)
+info: |
+ Intl.Locale( tag [, options] )
+ 9. Else,
+ a. Let tag be ? ToString(tag).
+ 10. If options is undefined, then
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
+ ...
+ 9. If tag matches neither the privateuse nor the grandfathered production, then
+ ...
+ c. If script is not undefined, then
+ i. If tag does not contain a script production, then
+ 1. Set tag to the concatenation of the language production of tag, "-", script, and the rest of tag.
+ ii. Else,
+ 1. Set tag to tag with the substring corresponding to the script production replaced by the string script.
+
+
+features: [Intl.Locale]
+---*/
+
+const validScriptOptions = [
+ [null, 'Null'],
+ ['bali', 'Bali'],
+ ['Bali', 'Bali'],
+ ['bALI', 'Bali'],
+ [{ toString() { return 'Brai' } }, 'Brai'],
+];
+for (const [script, expected] of validScriptOptions) {
+ let expect = expected ? 'en-' + expected : 'en';
+
+ assert.sameValue(
+ new Intl.Locale('en', { script }).toString(),
+ expect,
+ `new Intl.Locale("en", {script: "${script}"}).toString() returns "${expect}"`
+ );
+
+ expect = (expected ? ('en-' + expected) : 'en') + '-DK';
+ assert.sameValue(
+ new Intl.Locale('en-DK', { script }).toString(),
+ expect,
+ `new Intl.Locale("en-DK", {script: "${script}"}).toString() returns "${expect}"`
+ );
+
+ expect = expected ? ('en-' + expected) : 'en-Cyrl';
+ assert.sameValue(
+ new Intl.Locale('en-Cyrl', { script }).toString(),
+ expect,
+ `new Intl.Locale("en-Cyrl", {script: "${script}"}).toString() returns "${expect}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-options-throwing-getters.js b/js/src/tests/test262/intl402/Locale/constructor-options-throwing-getters.js
new file mode 100644
index 0000000000..92418505ad
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-options-throwing-getters.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale
+description: Checks the propagation of exceptions from the options for the Locale constructor.
+features: [Intl.Locale]
+---*/
+
+function CustomError() {}
+
+const options = [
+ "language",
+ "script",
+ "region",
+ "calendar",
+ "collation",
+ "hourCycle",
+ "caseFirst",
+ "numeric",
+ "numberingSystem",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.Locale("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ },
+ `new Intl.Locale("en", {get ${option}() {throw new CustomError();}}) throws CustomError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-parse-twice.js b/js/src/tests/test262/intl402/Locale/constructor-parse-twice.js
new file mode 100644
index 0000000000..361c2d107d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-parse-twice.js
@@ -0,0 +1,60 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the handling of options with grandfathered tags.
+info: |
+ Intl.Locale( tag [, options] )
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+ 14. Let calendar be ? GetOption(options, "calendar", "string", undefined, undefined).
+ 16. Set opt.[[ca]] to calendar.
+ 30. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
+
+ ApplyOptionsToTag( tag, options )
+ ...
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+
+ IsStructurallyValidLanguageTag ( locale )
+
+ The IsStructurallyValidLanguageTag abstract operation verifies that the
+ locale argument (which must be a String value)
+
+ represents a well-formed Unicode BCP 47 Locale Identifier" as specified in
+ Unicode Technical Standard 35 section 3.2, or successor,
+
+features: [Intl.Locale]
+---*/
+
+const testData = [
+ // Canonicalized version of "en-GB-oed", which we can add "US" to right away.
+ {
+ tag: "en-GB-oxendict",
+ options: {
+ region: "US",
+ calendar: "gregory",
+ },
+ canonical: "en-US-oxendict-u-ca-gregory",
+ },
+];
+
+for (const {tag, options, canonical} of testData) {
+ assert.sameValue(
+ new Intl.Locale(tag, options).toString(),
+ canonical,
+ `new Intl.Locale("${tag}", ${options}).toString() returns "${canonical}"`
+ );
+}
+
+assert.throws(RangeError, () =>
+ new Intl.Locale("no-bok", {region: "NO", calendar: "gregory"}));
+assert.throws(RangeError, () =>
+ new Intl.Locale("no-bok", {region: "SE", calendar: "gregory"}));
+assert.throws(RangeError, () =>
+ new Intl.Locale("no-bok-NO", {region: "SE", calendar: "gregory"}));
+assert.throws(RangeError, () =>
+ new Intl.Locale("no-bok-SE", {region: "NO", calendar: "gregory"}));
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-tag-tostring.js b/js/src/tests/test262/intl402/Locale/constructor-tag-tostring.js
new file mode 100644
index 0000000000..21472ca467
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-tag-tostring.js
@@ -0,0 +1,39 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the string conversion of the locale argument to the
+ Locale constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 8. If Type(tag) is Object and tag has an [[InitializedLocale]] internal slot, then
+ 9. Else,
+ a. Let tag be ? ToString(tag).
+features: [Intl.Locale]
+---*/
+
+function CustomError() {}
+function WrongCustomError() {}
+
+const errors = [
+ { get [Symbol.toPrimitive]() { throw new CustomError(); } },
+ { [Symbol.toPrimitive](hint) { assert.sameValue(hint, "string"); throw new CustomError(); } },
+ { get toString() { throw new CustomError(); }, get valueOf() { throw new WrongCustomError(); } },
+ { toString() { throw new CustomError(); }, get valueOf() { throw new WrongCustomError(); } },
+ { toString: undefined, get valueOf() { throw new CustomError(); } },
+ { toString: undefined, valueOf() { throw new CustomError(); } },
+ { toString() { return {} }, get valueOf() { throw new CustomError(); } },
+ { toString() { return {} }, valueOf() { throw new CustomError(); } },
+];
+
+for (const input of errors) {
+ assert.throws(CustomError, function() {
+ new Intl.Locale(input);
+ });
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-tag.js b/js/src/tests/test262/intl402/Locale/constructor-tag.js
new file mode 100644
index 0000000000..e84fcdbfb5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-tag.js
@@ -0,0 +1,59 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ ...
+ 13. Return CanonicalizeLanguageTag(tag).
+features: [Intl.Locale]
+---*/
+
+const validLanguageTags = {
+ "eN": "en",
+ "en-gb": "en-GB",
+ "IT-LATN-iT": "it-Latn-IT",
+ "th-th-u-nu-thai": "th-TH-u-nu-thai",
+ "en-x-u-foo": "en-x-u-foo",
+ "en-a-bar-x-u-foo": "en-a-bar-x-u-foo",
+ "en-x-u-foo-a-bar": "en-x-u-foo-a-bar",
+ "en-u-baz-a-bar-x-u-foo": "en-a-bar-u-baz-x-u-foo",
+ "DE-1996": "de-1996", // unicode_language_subtag sep unicode_variant_subtag
+
+ // unicode_language_subtag (sep unicode_variant_subtag)*
+ "sl-ROZAJ-BISKE-1994": "sl-1994-biske-rozaj",
+ "zh-latn-pinyin-pinyin2": "zh-Latn-pinyin-pinyin2",
+};
+
+for (const [langtag, canonical] of Object.entries(validLanguageTags)) {
+ assert.sameValue(
+ new Intl.Locale(canonical).toString(),
+ canonical,
+ `new Intl.Locale("${canonical}").toString() returns "${canonical}"`
+ );
+ assert.sameValue(
+ new Intl.Locale(langtag).toString(),
+ canonical,
+ `new Intl.Locale("${langtag}").toString() returns "${canonical}"`
+ );
+}
+
+// unicode_language_subtag = alpha{2,3} | alpha{5,8};
+const invalidLanguageTags = [
+ "X-u-foo",
+ "Flob",
+ "ZORK",
+ "Blah-latn",
+ "QuuX-latn-us",
+ "SPAM-gb-x-Sausages-BACON-eggs",
+];
+
+for (const langtag of invalidLanguageTags) {
+ assert.throws(RangeError, () => new Intl.Locale(langtag));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-invalid.js b/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-invalid.js
new file mode 100644
index 0000000000..e069053dc5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-invalid.js
@@ -0,0 +1,33 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies treatment of specific structurally invalid tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+features: [Intl.Locale]
+---*/
+
+const invalidLanguageTags = [
+ // Unicode extension sequence is incomplete.
+ "da-u",
+ "da-u-",
+ "da-u--",
+ "da-u-t-latn",
+ "da-u-x-priv",
+
+ // Duplicate 'u' singleton.
+ "da-u-ca-gregory-u-ca-buddhist"
+];
+
+for (const langtag of invalidLanguageTags) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale(langtag)
+ },
+ `new Intl.Locale("${langtag}") throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-valid.js b/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-valid.js
new file mode 100644
index 0000000000..20c37c7981
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/constructor-unicode-ext-valid.js
@@ -0,0 +1,40 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ 10. Return CanonicalizeLanguageTag(tag).
+features: [Intl.Locale]
+---*/
+
+const validLanguageTags = {
+ // Duplicate keywords are removed.
+ "da-u-ca-gregory-ca-buddhist": "da-u-ca-gregory",
+
+ // Keywords currently used in Intl specs are reordered in US-ASCII order.
+ "zh-u-nu-hans-ca-chinese": "zh-u-ca-chinese-nu-hans",
+ "zh-u-ca-chinese-nu-hans": "zh-u-ca-chinese-nu-hans",
+
+ // Even keywords currently not used in Intl specs are reordered in US-ASCII order.
+ "de-u-cu-eur-nu-latn": "de-u-cu-eur-nu-latn",
+ "de-u-nu-latn-cu-eur": "de-u-cu-eur-nu-latn",
+
+ // Attributes in Unicode extensions are reordered in US-ASCII order.
+ "pt-u-attr-ca-gregory": "pt-u-attr-ca-gregory",
+ "pt-u-attr1-attr2-ca-gregory": "pt-u-attr1-attr2-ca-gregory",
+ "pt-u-attr2-attr1-ca-gregory": "pt-u-attr1-attr2-ca-gregory",
+};
+
+for (const [langtag, canonical] of Object.entries(validLanguageTags)) {
+ assert.sameValue(
+ new Intl.Locale(langtag).toString(),
+ canonical,
+ `new Intl.Locale("${langtag}").toString() returns "${canonical}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/extensions-grandfathered.js b/js/src/tests/test262/intl402/Locale/extensions-grandfathered.js
new file mode 100644
index 0000000000..af4f9e0abc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/extensions-grandfathered.js
@@ -0,0 +1,69 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies handling of options with grandfathered tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+ ...
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+
+ IsStructurallyValidLanguageTag ( locale )
+
+ The IsStructurallyValidLanguageTag abstract operation verifies that the
+ locale argument (which must be a String value)
+
+ represents a well-formed Unicode BCP 47 Locale Identifier" as specified in
+ Unicode Technical Standard 35 section 3.2, or successor,
+
+features: [Intl.Locale]
+---*/
+
+const testData = [
+ // Regular grandfathered without modern replacement.
+ {
+ tag: "cel-gaulish",
+ options: {
+ language: "fr",
+ script: "Cyrl",
+ region: "FR",
+ numberingSystem: "latn",
+ },
+ canonical: "fr-Cyrl-FR-u-nu-latn",
+ },
+
+ // Regular grandfathered with modern replacement.
+ {
+ tag: "art-lojban",
+ options: {
+ language: "fr",
+ script: "Cyrl",
+ region: "ZZ",
+ numberingSystem: "latn",
+ },
+ canonical: "fr-Cyrl-ZZ-u-nu-latn",
+ },
+];
+
+for (const {tag, options, canonical} of testData) {
+ const loc = new Intl.Locale(tag, options);
+ assert.sameValue(loc.toString(), canonical);
+
+ for (const [name, value] of Object.entries(options)) {
+ assert.sameValue(loc[name], value);
+ }
+}
+
+assert.throws(RangeError, () =>
+ new Intl.Locale("i-default",
+ {language: "fr", script: "Cyrl", region: "DE", numberingSystem: "latn"}
+ ));
+
+assert.throws(RangeError, () =>
+ new Intl.Locale("en-gb-oed",
+ {language: "fr", script: "Cyrl", region: "US", numberingSystem: "latn"}
+ ));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/extensions-private.js b/js/src/tests/test262/intl402/Locale/extensions-private.js
new file mode 100644
index 0000000000..cd3771b57f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/extensions-private.js
@@ -0,0 +1,26 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies handling of options with privateuse tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+
+
+ ...
+ 9. If tag matches neither the privateuse nor the grandfathered production, then
+ ...
+
+features: [Intl.Locale]
+---*/
+
+assert.throws(RangeError, () => new Intl.Locale("x-default", {
+ language: "fr",
+ script: "Cyrl",
+ region: "DE",
+ numberingSystem: "latn",
+}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/function-prototype.js b/js/src/tests/test262/intl402/Locale/function-prototype.js
new file mode 100644
index 0000000000..6127b8d129
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/function-prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ The value of the [[Prototype]] internal slot of the Intl.Locale constructor is the
+ intrinsic object %FunctionPrototype%.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ Object.getPrototypeOf(Intl.Locale),
+ Function.prototype,
+ "Object.getPrototypeOf(Intl.Locale) equals the value of Function.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/getters-grandfathered.js b/js/src/tests/test262/intl402/Locale/getters-grandfathered.js
new file mode 100644
index 0000000000..9df709545b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/getters-grandfathered.js
@@ -0,0 +1,41 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies getters with grandfathered tags.
+info: |
+ get Intl.Locale.prototype.baseName
+ 5. Return the substring of locale corresponding to the
+ language ["-" script] ["-" region] *("-" variant)
+ subsequence of the unicode_language_id grammar.
+
+ get Intl.Locale.prototype.language
+ 5. Return the substring of locale corresponding to the
+ unicode_language_subtag production.
+
+ get Intl.Locale.prototype.script
+ 6. Return the substring of locale corresponding to the
+ unicode_script_subtag production.
+
+ get Intl.Locale.prototype.region
+ 6. Return the substring of locale corresponding to the unicode_region_subtag
+ production.
+features: [Intl.Locale]
+---*/
+
+// Regular grandfathered language tag.
+var loc = new Intl.Locale("cel-gaulish");
+assert.sameValue(loc.baseName, "xtg");
+assert.sameValue(loc.language, "xtg");
+assert.sameValue(loc.script, undefined);
+assert.sameValue(loc.region, undefined);
+
+// Regular grandfathered language tag.
+assert.throws(RangeError, () => new Intl.Locale("zh-min"));
+
+assert.throws(RangeError, () => new Intl.Locale("i-default"));
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/getters-missing.js b/js/src/tests/test262/intl402/Locale/getters-missing.js
new file mode 100644
index 0000000000..2eaaec7282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/getters-missing.js
@@ -0,0 +1,55 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies getters with missing tags.
+info: |
+ get Intl.Locale.prototype.baseName
+ 5. Return the substring of locale corresponding to the
+ language ["-" script] ["-" region] *("-" variant)
+ subsequence of the langtag grammar.
+
+ get Intl.Locale.prototype.language
+ 4. Return the substring of locale corresponding to the language production.
+
+ get Intl.Locale.prototype.script
+ 6. If locale does not contain the ["-" script] sequence, return undefined.
+ 7. Return the substring of locale corresponding to the script production.
+
+ get Intl.Locale.prototype.region
+ 6. If locale does not contain the ["-" region] sequence, return undefined.
+ 7. Return the substring of locale corresponding to the region production.
+features: [Intl.Locale]
+---*/
+
+// 'script' and 'region' subtags not present.
+var loc = new Intl.Locale("sv");
+assert.sameValue(loc.baseName, "sv");
+assert.sameValue(loc.language, "sv");
+assert.sameValue(loc.script, undefined);
+assert.sameValue(loc.region, undefined);
+
+// 'region' subtag not present.
+var loc = new Intl.Locale("sv-Latn");
+assert.sameValue(loc.baseName, "sv-Latn");
+assert.sameValue(loc.language, "sv");
+assert.sameValue(loc.script, "Latn");
+assert.sameValue(loc.region, undefined);
+
+// 'script' subtag not present.
+var loc = new Intl.Locale("sv-SE");
+assert.sameValue(loc.baseName, "sv-SE");
+assert.sameValue(loc.language, "sv");
+assert.sameValue(loc.script, undefined);
+assert.sameValue(loc.region, "SE");
+
+// 'variant' subtag present.
+var loc = new Intl.Locale("de-1901");
+assert.sameValue(loc.baseName, "de-1901");
+assert.sameValue(loc.language, "de");
+assert.sameValue(loc.script, undefined);
+assert.sameValue(loc.region, undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/getters.js b/js/src/tests/test262/intl402/Locale/getters.js
new file mode 100644
index 0000000000..6454fb1460
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/getters.js
@@ -0,0 +1,124 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies getters with normal tags.
+info: |
+ Intl.Locale.prototype.toString ()
+ 3. Return loc.[[Locale]].
+
+ get Intl.Locale.prototype.baseName
+ 5. Return the substring of locale corresponding to the
+ language ["-" script] ["-" region] *("-" variant)
+ subsequence of the langtag grammar.
+
+ get Intl.Locale.prototype.language
+ 4. Return the substring of locale corresponding to the language production.
+
+ get Intl.Locale.prototype.script
+ 7. Return the substring of locale corresponding to the script production.
+
+ get Intl.Locale.prototype.region
+ 7. Return the substring of locale corresponding to the region production.
+
+ get Intl.Locale.prototype.calendar
+ 3. Return loc.[[Calendar]].
+
+ get Intl.Locale.prototype.collation
+ 3. Return loc.[[Collation]].
+
+ get Intl.Locale.prototype.hourCycle
+ 3. Return loc.[[HourCycle]].
+
+ get Intl.Locale.prototype.caseFirst
+ This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kf".
+ 3. Return loc.[[CaseFirst]].
+
+ get Intl.Locale.prototype.numeric
+ This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kn".
+ 3. Return loc.[[Numeric]].
+
+ get Intl.Locale.prototype.numberingSystem
+ 3. Return loc.[[NumberingSystem]].
+
+features: [Intl.Locale]
+---*/
+
+// Test all getters return the expected results.
+var langtag = "de-latn-de-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn";
+var loc = new Intl.Locale(langtag);
+
+assert.sameValue(loc.toString(), "de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-kn-false-nu-latn");
+assert.sameValue(loc.baseName, "de-Latn-DE");
+assert.sameValue(loc.language, "de");
+assert.sameValue(loc.script, "Latn");
+assert.sameValue(loc.region, "DE");
+assert.sameValue(loc.calendar, "gregory");
+assert.sameValue(loc.collation, "phonebk");
+assert.sameValue(loc.hourCycle, "h23");
+if ("caseFirst" in loc) {
+ assert.sameValue(loc.caseFirst, "");
+}
+if ("numeric" in loc) {
+ assert.sameValue(loc.numeric, false);
+}
+assert.sameValue(loc.numberingSystem, "latn");
+
+// Replace all components through option values and validate the getters still
+// return the expected results.
+var loc = new Intl.Locale(langtag, {
+ language: "ja",
+ script: "jpan",
+ region: "jp",
+ calendar: "japanese",
+ collation: "search",
+ hourCycle: "h24",
+ caseFirst: "false",
+ numeric: "true",
+ numberingSystem: "jpanfin",
+});
+
+assert.sameValue(loc.toString(), "ja-Jpan-JP-u-ca-japanese-co-search-hc-h24-kf-false-kn-nu-jpanfin");
+assert.sameValue(loc.baseName, "ja-Jpan-JP");
+assert.sameValue(loc.language, "ja");
+assert.sameValue(loc.script, "Jpan");
+assert.sameValue(loc.region, "JP");
+assert.sameValue(loc.calendar, "japanese");
+assert.sameValue(loc.collation, "search");
+assert.sameValue(loc.hourCycle, "h24");
+if ("caseFirst" in loc) {
+ assert.sameValue(loc.caseFirst, "false");
+}
+if ("numeric" in loc) {
+ assert.sameValue(loc.numeric, true);
+}
+assert.sameValue(loc.numberingSystem, "jpanfin");
+
+// Replace only some components through option values and validate the getters
+// return the expected results.
+var loc = new Intl.Locale(langtag, {
+ language: "fr",
+ region: "ca",
+ collation: "standard",
+ hourCycle: "h11",
+});
+
+assert.sameValue(loc.toString(), "fr-Latn-CA-u-ca-gregory-co-standard-hc-h11-kf-kn-false-nu-latn");
+assert.sameValue(loc.baseName, "fr-Latn-CA");
+assert.sameValue(loc.language, "fr");
+assert.sameValue(loc.script, "Latn");
+assert.sameValue(loc.region, "CA");
+assert.sameValue(loc.calendar, "gregory");
+assert.sameValue(loc.collation, "standard");
+assert.sameValue(loc.hourCycle, "h11");
+if ("caseFirst" in loc) {
+ assert.sameValue(loc.caseFirst, "");
+}
+if ("numeric" in loc) {
+ assert.sameValue(loc.numeric, false);
+}
+assert.sameValue(loc.numberingSystem, "latn");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/instance-extensibility.js b/js/src/tests/test262/intl402/Locale/instance-extensibility.js
new file mode 100644
index 0000000000..99a10021cb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/instance-extensibility.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Intl.Locale instance object extensibility
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Unless specified otherwise, the [[Extensible]] internal slot
+ of a built-in object initially has the value true.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ Object.isExtensible(new Intl.Locale('en')),
+ true,
+ "Object.isExtensible(new Intl.Locale('en')) returns true"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/instance.js b/js/src/tests/test262/intl402/Locale/instance.js
new file mode 100644
index 0000000000..24d1c2c5f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/instance.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Intl.Locale instance object created from %LocalePrototype%.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 6. Let locale be ?
+ OrdinaryCreateFromConstructor(NewTarget, %LocalePrototype%,
+ internalSlotsList).
+features: [Intl.Locale]
+---*/
+
+const value = new Intl.Locale('en');
+assert.sameValue(
+ Object.getPrototypeOf(value),
+ Intl.Locale.prototype,
+ "Object.getPrototypeOf(value) equals the value of Intl.Locale.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws-boolean.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-boolean.js
new file mode 100644
index 0000000000..a8ea22421d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-boolean.js
@@ -0,0 +1,24 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the type check on the tag argument to Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 7. If Type(tag) is not String or Object, throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(true);
+}, "true is an invalid tag value");
+assert.throws(TypeError, function() {
+ new Intl.Locale(false);
+}, "false is an invalid tag value");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws-null.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-null.js
new file mode 100644
index 0000000000..9078819cd4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-null.js
@@ -0,0 +1,21 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the type check on the tag argument to Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 7. If Type(tag) is not String or Object, throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(null);
+}, "null is an invalid tag value");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws-number.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-number.js
new file mode 100644
index 0000000000..28b1672f31
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-number.js
@@ -0,0 +1,34 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the type check on the tag argument to Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 7. If Type(tag) is not String or Object, throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(0);
+}, "0 is an invalid tag value");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(1);
+}, "1 is an invalid tag value");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(Infinity);
+}, "Infinity is an invalid tag value");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(NaN);
+}, "NaN is an invalid tag value");
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws-symbol.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-symbol.js
new file mode 100644
index 0000000000..887bd46da1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-symbol.js
@@ -0,0 +1,21 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the type check on the tag argument to Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 7. If Type(tag) is not String or Object, throw a TypeError exception.
+features: [Intl.Locale, Symbol]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(Symbol());
+}, "Symbol() is an invalid tag value");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws-undefined.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-undefined.js
new file mode 100644
index 0000000000..f8184c5a29
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws-undefined.js
@@ -0,0 +1,25 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies the type check on the tag argument to Intl.Locale.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 7. If Type(tag) is not String or Object, throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale();
+}, "(empty) is an invalid tag value");
+
+assert.throws(TypeError, function() {
+ new Intl.Locale(undefined)
+}, "undefined is an invalid tag value");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/invalid-tag-throws.js b/js/src/tests/test262/intl402/Locale/invalid-tag-throws.js
new file mode 100644
index 0000000000..7b16250812
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/invalid-tag-throws.js
@@ -0,0 +1,41 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks error cases for the options argument to the Locale
+ constructor.
+info: |
+ Intl.Locale( tag [, options] )
+
+ ...
+ 11. Else
+ a. Let options be ? ToObject(options).
+ 12. Set tag to ? ApplyOptionsToTag(tag, options).
+ ...
+
+ ApplyOptionsToTag( tag, options )
+
+ ...
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+ ...
+includes: [testIntl.js]
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+// Intl.Locale step 11.a.
+assert.throws(TypeError, function() { new Intl.Locale("en", null) })
+
+// ApplyOptionsToTag step 2.
+for (const invalidTag of getInvalidLanguageTags()) {
+ assert.throws(RangeError, function() {
+ new Intl.Locale(invalidTag);
+ }, `${invalidTag} is an invalid tag value`);
+}
+
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/length.js b/js/src/tests/test262/intl402/Locale/length.js
new file mode 100644
index 0000000000..34ad67c28e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "length" property of the Locale constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Locale constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/likely-subtags-grandfathered.js b/js/src/tests/test262/intl402/Locale/likely-subtags-grandfathered.js
new file mode 100644
index 0000000000..56c3fe493a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/likely-subtags-grandfathered.js
@@ -0,0 +1,200 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization, minimization and maximization of specific tags.
+info: |
+ ApplyOptionsToTag( tag, options )
+
+ 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
+
+ 9. Set tag to CanonicalizeLanguageTag(tag).
+
+ CanonicalizeLanguageTag( tag )
+
+ The CanonicalizeLanguageTag abstract operation returns the canonical and
+ case-regularized form of the locale argument (which must be a String value
+ that is a structurally valid Unicode BCP 47 Locale Identifier as verified by
+ the IsStructurallyValidLanguageTag abstract operation).
+
+ IsStructurallyValidLanguageTag ( locale )
+
+ The IsStructurallyValidLanguageTag abstract operation verifies that the
+ locale argument (which must be a String value)
+
+ represents a well-formed Unicode BCP 47 Locale Identifier" as specified in
+ Unicode Technical Standard 35 section 3.2, or successor,
+
+
+ Intl.Locale.prototype.maximize ()
+ 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]].
+
+ Intl.Locale.prototype.minimize ()
+ 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
+features: [Intl.Locale]
+---*/
+
+const irregularGrandfathered = [
+ "en-GB-oed",
+ "i-ami",
+ "i-bnn",
+ "i-default",
+ "i-enochian",
+ "i-hak",
+ "i-klingon",
+ "i-lux",
+ "i-mingo",
+ "i-navajo",
+ "i-pwn",
+ "i-tao",
+ "i-tay",
+ "i-tsu",
+ "sgn-BE-FR",
+ "sgn-BE-NL",
+ "sgn-CH-DE",
+];
+
+for (const tag of irregularGrandfathered) {
+ assert.throws(RangeError, () => new Intl.Locale(tag));
+}
+
+const regularGrandfathered = [
+ {
+ tag: "art-lojban",
+ canonical: "jbo",
+ maximized: "jbo-Latn-001",
+ },
+ {
+ tag: "cel-gaulish",
+ canonical: "xtg",
+ },
+ {
+ tag: "zh-guoyu",
+ canonical: "zh",
+ maximized: "zh-Hans-CN",
+ },
+ {
+ tag: "zh-hakka",
+ canonical: "hak",
+ maximized: "hak-Hans-CN",
+ },
+ {
+ tag: "zh-xiang",
+ canonical: "hsn",
+ maximized: "hsn-Hans-CN",
+ },
+];
+
+for (const {tag, canonical, maximized = canonical, minimized = canonical} of regularGrandfathered) {
+ const loc = new Intl.Locale(tag);
+ assert.sameValue(loc.toString(), canonical);
+
+ assert.sameValue(loc.maximize().toString(), maximized);
+ assert.sameValue(loc.maximize().maximize().toString(), maximized);
+
+ assert.sameValue(loc.minimize().toString(), minimized);
+ assert.sameValue(loc.minimize().minimize().toString(), minimized);
+
+ assert.sameValue(loc.maximize().minimize().toString(), minimized);
+ assert.sameValue(loc.minimize().maximize().toString(), maximized);
+}
+
+const regularGrandfatheredWithExtLang = [
+ "no-bok",
+ "no-nyn",
+ "zh-min",
+ "zh-min-nan",
+];
+
+for (const tag of regularGrandfatheredWithExtLang) {
+ assert.throws(RangeError, () => new Intl.Locale(tag));
+}
+
+// Add variants, extensions, and privateuse subtags to regular grandfathered
+// language tags and ensure it produces the "expected" result.
+const extras = [
+ "fonipa",
+ "a-not-assigned",
+ "u-attr",
+ "u-co",
+ "u-co-phonebk",
+ "x-private",
+];
+
+for (const {tag, canonical} of regularGrandfathered) {
+ const priv = "-x-0";
+ const tagMax = new Intl.Locale(canonical + priv).maximize().toString().slice(0, -priv.length);
+ const tagMin = new Intl.Locale(canonical + priv).minimize().toString().slice(0, -priv.length);
+
+ for (const extra of extras) {
+ const loc = new Intl.Locale(tag + "-" + extra);
+
+ let canonicalWithExtra = canonical + "-" + extra;
+ let canonicalMax = tagMax + "-" + extra;
+ let canonicalMin = tagMin + "-" + extra;
+
+ // Ensure the added variant subtag is correctly sorted in the canonical tag.
+ if (/^[a-z0-9]{5,8}|[0-9][a-z0-9]{3}$/i.test(extra)) {
+ const sorted = s => s.replace(/(-([a-z0-9]{5,8}|[0-9][a-z0-9]{3}))+$/i,
+ m => m.split("-").sort().join("-"));
+ canonicalWithExtra = sorted(canonicalWithExtra);
+ canonicalMax = sorted(canonicalMax);
+ canonicalMin = sorted(canonicalMin);
+ }
+
+ // Adding extra subtags to grandfathered tags can have "interesting" results. Take for
+ // example "art-lojban" when "fonipa" is added, so we get "art-lojban-fonipa". The first
+ // step when canonicalising the language tag is to bring it in 'canonical syntax', that
+ // means among other things sorting variants in alphabetical order. So "art-lojban-fonipa"
+ // is transformed to "art-fonipa-lojban", because "fonipa" is sorted before "lojban". And
+ // only after that has happened, we replace aliases with their preferred form.
+ //
+ // Now the usual problems arise when doing silly things like adding subtags to
+ // grandfathered subtags, nobody, neither RFC 5646 nor UTS 35, provides a clear description
+ // what needs to happen next.
+ //
+ // From <http://unicode.org/reports/tr35/#Language_Tag_to_Locale_Identifier>:
+ //
+ // > A valid [BCP47] language tag can be converted to a valid Unicode BCP 47 locale
+ // > identifier according to Annex C. LocaleId Canonicalization
+ //
+ // From <http://unicode.org/reports/tr35/#LocaleId_Canonicalization>
+ // > The languageAlias, scriptAlias, territoryAlias, and variantAlias elements are used
+ // > as rules to transform an input source localeId. The first step is to transform the
+ // > languageId portion of the localeId.
+ //
+ // For regular grandfathered tags, "lojban", "gaulish", "guoyu", "hakka", and "xiang" will
+ // therefore be considered as the "variant" subtag and be replaced by rules in languageAlias.
+ //
+ // Not all language tag processor will pass this test, for example because they don't order
+ // variant subtags in alphabetical order or they're too eager when detecting grandfathered
+ // tags. For example "zh-hakka-hakka" is accepted in some language tag processors, because
+ // the language tag starts with a prefix which matches a grandfathered tag, and that prefix
+ // is then canonicalised to "hak" and the second "hakka" is simply appended to it, so the
+ // resulting tag is "hak-hakka". This is clearly wrong as far as ECMA-402 compliance is
+ // concerned, because language tags are parsed and validated before any canonicalisation
+ // happens. And during the validation step an error should be emitted, because the input
+ // "zh-hakka-hakka" contains two identical variant subtags.
+ //
+ // From <https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag>:
+ //
+ // > does not include duplicate variant subtags
+ //
+ // So, if your implementation fails this assertion, but you still like to test the rest of
+ // this file, a pull request to split this file seems the way to go!
+ assert.sameValue(loc.toString(), canonicalWithExtra);
+
+ assert.sameValue(loc.maximize().toString(), canonicalMax);
+ assert.sameValue(loc.maximize().maximize().toString(), canonicalMax);
+
+ assert.sameValue(loc.minimize().toString(), canonicalMin);
+ assert.sameValue(loc.minimize().minimize().toString(), canonicalMin);
+
+ assert.sameValue(loc.maximize().minimize().toString(), canonicalMin);
+ assert.sameValue(loc.minimize().maximize().toString(), canonicalMax);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/likely-subtags.js b/js/src/tests/test262/intl402/Locale/likely-subtags.js
new file mode 100644
index 0000000000..403ea2c7f2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/likely-subtags.js
@@ -0,0 +1,115 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Verifies canonicalization, minimization and maximization of specific tags.
+info: |
+ Intl.Locale.prototype.maximize ()
+ 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]].
+
+ Intl.Locale.prototype.minimize ()
+ 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
+features: [Intl.Locale]
+---*/
+
+const testDataMaximal = {
+ // Language subtag is present.
+ "en": "en-Latn-US",
+
+ // Language and script subtags are present.
+ "en-Latn": "en-Latn-US",
+ "en-Shaw": "en-Shaw-GB",
+ "en-Arab": "en-Arab-US",
+
+ // Language and region subtags are present.
+ "en-US": "en-Latn-US",
+ "en-GB": "en-Latn-GB",
+ "en-FR": "en-Latn-FR",
+
+ // Language, script, and region subtags are present.
+ "it-Kana-CA": "it-Kana-CA",
+
+ // Undefined primary language.
+ "und": "en-Latn-US",
+ "und-Thai": "th-Thai-TH",
+ "und-419": "es-Latn-419",
+ "und-150": "en-Latn-150",
+ "und-AT": "de-Latn-AT",
+ "und-Cyrl-RO": "bg-Cyrl-RO",
+
+ // Before CLDR 44, "und" primary language subtag was left unchanged in some
+ // cases. Starting with CLDR 44, the "und" language subtag is always replaced.
+ "und-AQ": "en-Latn-AQ",
+};
+
+const testDataMinimal = {
+ // Language subtag is present.
+ "en": "en",
+
+ // Language and script subtags are present.
+ "en-Latn": "en",
+ "ar-Arab": "ar",
+
+ // Language and region subtags are present.
+ "en-US": "en",
+ "en-GB": "en-GB",
+
+ // Reverse cases from |testDataMaximal|.
+ "en-Latn-US": "en",
+ "en-Shaw-GB": "en-Shaw",
+ "en-Arab-US": "en-Arab",
+ "en-Latn-GB": "en-GB",
+ "en-Latn-FR": "en-FR",
+ "it-Kana-CA": "it-Kana-CA",
+ "th-Thai-TH": "th",
+ "es-Latn-419": "es-419",
+ "ru-Cyrl-RU": "ru",
+ "de-Latn-AT": "de-AT",
+ "bg-Cyrl-RO": "bg-RO",
+ "und-Latn-AQ": "en-AQ",
+};
+
+// Add variants, extensions, and privateuse subtags and ensure they don't
+// modify the result of the likely subtags algorithms.
+const extras = [
+ "",
+ "-fonipa",
+ "-a-not-assigned",
+ "-u-attr",
+ "-u-co",
+ "-u-co-phonebk",
+ "-x-private",
+];
+
+for (const [tag, maximal] of Object.entries(testDataMaximal)) {
+ assert.sameValue(new Intl.Locale(maximal).maximize().toString(), maximal,
+ `"${maximal}" should be maximal`);
+
+ for (const extra of extras) {
+ const input = tag + extra;
+ const output = maximal + extra;
+ assert.sameValue(new Intl.Locale(input).maximize().toString(), output,
+ `"${input}".maximize() should be "${output}"`);
+ }
+}
+
+for (const [tag, minimal] of Object.entries(testDataMinimal)) {
+ assert.sameValue(new Intl.Locale(minimal).minimize().toString(), minimal,
+ `"${minimal}" should be minimal`);
+
+ for (const extra of extras) {
+ const input = tag + extra;
+ const output = minimal + extra;
+ assert.sameValue(new Intl.Locale(input).minimize().toString(), output,
+ `"${input}".minimize() should be "${output}"`);
+ }
+}
+
+// privateuse only.
+// "x" in "x-private" does not match unicode_language_subtag
+// unicode_language_subtag = alpha{2,3} | alpha{5,8};
+assert.throws(RangeError, () => new Intl.Locale("x-private"));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/name.js b/js/src/tests/test262/intl402/Locale/name.js
new file mode 100644
index 0000000000..0def17d420
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "name" property of the Locale constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale, "name", {
+ value: "Locale",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prop-desc.js b/js/src/tests/test262/intl402/Locale/prop-desc.js
new file mode 100644
index 0000000000..aab4c6aa7f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prop-desc.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ "Locale" property of Intl.
+info: |
+ Intl.Locale (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl, "Locale", {
+ value: Intl.Locale,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/Locale/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..3973759844
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/proto-from-ctor-realm.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.Locale ( tag [ , options] )
+
+ ...
+ 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, %LocalePrototype%, internalSlotsList).
+ ...
+ 38. Return locale.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.Locale, cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var locale;
+
+newTarget.prototype = undefined;
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = true;
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 0;
+locale = Reflect.construct(Intl.Locale, ['de'], newTarget);
+assert.sameValue(Object.getPrototypeOf(locale), other.Intl.Locale.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/baseName/branding.js b/js/src/tests/test262/intl402/Locale/prototype/baseName/branding.js
new file mode 100644
index 0000000000..8c8264aa0f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/baseName/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.baseName
+description: >
+ Verifies the branding check for the "baseName" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.baseName
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "baseName");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/baseName/browser.js b/js/src/tests/test262/intl402/Locale/prototype/baseName/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/baseName/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/baseName/name.js b/js/src/tests/test262/intl402/Locale/prototype/baseName/name.js
new file mode 100644
index 0000000000..f714d0a49a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/baseName/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.baseName
+description: >
+ Checks the "name" property of Intl.Locale.prototype.baseName.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "baseName").get;
+verifyProperty(getter, "name", {
+ value: "get baseName",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/baseName/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/baseName/prop-desc.js
new file mode 100644
index 0000000000..e9050e724c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/baseName/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "baseName" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.baseName
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "baseName");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "baseName", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/baseName/shell.js b/js/src/tests/test262/intl402/Locale/prototype/baseName/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/baseName/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/browser.js b/js/src/tests/test262/intl402/Locale/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/calendar/branding.js b/js/src/tests/test262/intl402/Locale/prototype/calendar/branding.js
new file mode 100644
index 0000000000..27ae523895
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/calendar/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.calendar
+description: >
+ Verifies the branding check for the "calendar" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.calendar
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "calendar");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/calendar/browser.js b/js/src/tests/test262/intl402/Locale/prototype/calendar/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/calendar/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/calendar/name.js b/js/src/tests/test262/intl402/Locale/prototype/calendar/name.js
new file mode 100644
index 0000000000..ea316300a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/calendar/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.calendar
+description: >
+ Checks the "name" property of Intl.Locale.prototype.calendar.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "calendar").get;
+verifyProperty(getter, "name", {
+ value: "get calendar",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/calendar/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/calendar/prop-desc.js
new file mode 100644
index 0000000000..7e125216f2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/calendar/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "calendar" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.calendar
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "calendar");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "calendar", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/calendar/shell.js b/js/src/tests/test262/intl402/Locale/prototype/calendar/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/calendar/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/caseFirst/branding.js b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/branding.js
new file mode 100644
index 0000000000..0506338c37
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/branding.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.caseFirst
+description: >
+ Verifies the branding check for the "caseFirst" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.caseFirst
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "caseFirst");
+if (propdesc !== undefined) {
+ const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+ ];
+
+ for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/caseFirst/browser.js b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/caseFirst/name.js b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/name.js
new file mode 100644
index 0000000000..6b1f6fcd64
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/name.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.caseFirst
+description: >
+ Checks the "name" property of Intl.Locale.prototype.caseFirst.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "caseFirst");
+if (propdesc !== undefined) {
+ const getter = propdesc.get;
+ verifyProperty(getter, "name", {
+ value: "get caseFirst",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ });
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/caseFirst/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/prop-desc.js
new file mode 100644
index 0000000000..d9528fab7f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/prop-desc.js
@@ -0,0 +1,29 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "caseFirst" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.caseFirst
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "caseFirst");
+if (propdesc) {
+ assert.sameValue(propdesc.set, undefined);
+ assert.sameValue(typeof propdesc.get, "function");
+
+ verifyProperty(Intl.Locale.prototype, "caseFirst", {
+ enumerable: false,
+ configurable: true,
+ });
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/caseFirst/shell.js b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/caseFirst/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/collation/branding.js b/js/src/tests/test262/intl402/Locale/prototype/collation/branding.js
new file mode 100644
index 0000000000..e3e2eb5794
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/collation/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.collation
+description: >
+ Verifies the branding check for the "collation" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.collation
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "collation");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/collation/browser.js b/js/src/tests/test262/intl402/Locale/prototype/collation/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/collation/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/collation/name.js b/js/src/tests/test262/intl402/Locale/prototype/collation/name.js
new file mode 100644
index 0000000000..a45593e266
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/collation/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.collation
+description: >
+ Checks the "name" property of Intl.Locale.prototype.collation.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "collation").get;
+verifyProperty(getter, "name", {
+ value: "get collation",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/collation/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/collation/prop-desc.js
new file mode 100644
index 0000000000..46de900d7b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/collation/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "collation" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.collation
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "collation");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "collation", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/collation/shell.js b/js/src/tests/test262/intl402/Locale/prototype/collation/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/collation/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/constructor/browser.js b/js/src/tests/test262/intl402/Locale/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..e0cff7c1e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/constructor/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.constructor
+description: >
+ Checks the "constructor" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.constructor
+
+ The initial value of Intl.Locale.prototype.constructor is %Locale%.
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale.prototype, 'constructor', {
+ value: Intl.Locale,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/constructor/shell.js b/js/src/tests/test262/intl402/Locale/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/branding.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/branding.js
new file mode 100644
index 0000000000..5907a7c5e8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/branding.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.firstDayOfWeek
+description: >
+ Verifies the branding check for the "firstDayOfWeek" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.firstDayOfWeek
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "firstDayOfWeek");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/browser.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/name.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/name.js
new file mode 100644
index 0000000000..2f51b4b268
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/name.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.firstDayOfWeek
+description: >
+ Checks the "name" property of Intl.Locale.prototype.firstDayOfWeek.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "firstDayOfWeek").get;
+verifyProperty(getter, "name", {
+ value: "get firstDayOfWeek",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/prop-desc.js
new file mode 100644
index 0000000000..9d1c8f2004
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "firstDayOfWeek" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.firstDayOfWeek
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "firstDayOfWeek");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "firstDayOfWeek", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/shell.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-id.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-id.js
new file mode 100644
index 0000000000..2f723a409a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-id.js
@@ -0,0 +1,33 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale.prototype.firstDayOfWeek
+ 3. Return loc.[[FirstDayOfWeek]].
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const validIds = [
+ ["en-u-fw-mon", 1],
+ ["en-u-fw-tue", 2],
+ ["en-u-fw-wed", 3],
+ ["en-u-fw-thu", 4],
+ ["en-u-fw-fri", 5],
+ ["en-u-fw-sat", 6],
+ ["en-u-fw-sun", 7],
+];
+for (const [id, expected] of validIds) {
+ assert.sameValue(
+ new Intl.Locale(id).firstDayOfWeek,
+ expected,
+ `new Intl.Locale(${id}).firstDayOfWeek returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-options.js b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-options.js
new file mode 100644
index 0000000000..00efebee33
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/firstDayOfWeek/valid-options.js
@@ -0,0 +1,54 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks valid cases for the options argument to the Locale constructor.
+info: |
+ Intl.Locale.prototype.firstDayOfWeek
+ 3. Return loc.[[FirstDayOfWeek]].
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const validFirstDayOfWeekOptions = [
+ ["mon", 1],
+ ["tue", 2],
+ ["wed", 3],
+ ["thu", 4],
+ ["fri", 5],
+ ["sat", 6],
+ ["sun", 7],
+ ["1", 1],
+ ["2", 2],
+ ["3", 3],
+ ["4", 4],
+ ["5", 5],
+ ["6", 6],
+ ["7", 7],
+ ["0", 7],
+ [1, 1],
+ [2, 2],
+ [3, 3],
+ [4, 4],
+ [5, 5],
+ [6, 6],
+ [7, 7],
+ [0, 7],
+];
+for (const [firstDayOfWeek, expected] of validFirstDayOfWeekOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', { firstDayOfWeek }).firstDayOfWeek,
+ expected,
+ `new Intl.Locale("en", { firstDayOfWeek: ${firstDayOfWeek} }).firstDayOfWeek returns "${expected}"`
+ );
+ assert.sameValue(
+ new Intl.Locale('en-u-fw-WED', { firstDayOfWeek }).firstDayOfWeek,
+ expected,
+ `new Intl.Locale("en-u-fw-WED", { firstDayOfWeek: ${firstDayOfWeek} }).firstDayOfWeek returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/branding.js
new file mode 100644
index 0000000000..b96024e71e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCalendars
+description: Verifies the branding check for the "getCalendars" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getCalendars ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getCalendars = Intl.Locale.prototype.getCalendars;
+
+assert.sameValue(typeof getCalendars, "function");
+
+assert.throws(TypeError, () => getCalendars.call(undefined), "undefined");
+assert.throws(TypeError, () => getCalendars.call(null), "null");
+assert.throws(TypeError, () => getCalendars.call(true), "true");
+assert.throws(TypeError, () => getCalendars.call(""), "empty string");
+assert.throws(TypeError, () => getCalendars.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getCalendars.call(1), "1");
+assert.throws(TypeError, () => getCalendars.call({}), "plain object");
+assert.throws(TypeError, () => getCalendars.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getCalendars.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/name.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/name.js
new file mode 100644
index 0000000000..faf5e8f723
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCalendars
+description: Checks the "name" property of Intl.Locale.prototype.getCalendars().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getCalendars, "name", {
+ value: "getCalendars",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/output-array.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/output-array.js
new file mode 100644
index 0000000000..d52bca8fc9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/output-array.js
@@ -0,0 +1,19 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getCalendars
+description: >
+ Checks that the return value of Intl.Locale.prototype.getCalendars is an Array.
+info: |
+ CalendarsOfLocale ( loc )
+ ...
+ 5. Return ! CreateArrayFromListAndPreferred( list, preferred ).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert(Array.isArray(new Intl.Locale('en').getCalendars()));
+assert(new Intl.Locale('en').getCalendars().length > 0, 'array has at least one element');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/prop-desc.js
new file mode 100644
index 0000000000..87cf922ef0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCalendars
+description: Checks the "getCalendars" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getCalendars ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getCalendars,
+ "function",
+ "typeof Intl.Locale.prototype.getCalendars is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getCalendars", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCalendars/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCalendars/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/branding.js
new file mode 100644
index 0000000000..c0f1038dc3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCollations
+description: Verifies the branding check for the "getCollations" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getCollations ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getCollations = Intl.Locale.prototype.getCollations;
+
+assert.sameValue(typeof getCollations, "function");
+
+assert.throws(TypeError, () => getCollations.call(undefined), "undefined");
+assert.throws(TypeError, () => getCollations.call(null), "null");
+assert.throws(TypeError, () => getCollations.call(true), "true");
+assert.throws(TypeError, () => getCollations.call(""), "empty string");
+assert.throws(TypeError, () => getCollations.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getCollations.call(1), "1");
+assert.throws(TypeError, () => getCollations.call({}), "plain object");
+assert.throws(TypeError, () => getCollations.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getCollations.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/name.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/name.js
new file mode 100644
index 0000000000..58cdc8538b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCollations
+description: Checks the "name" property of Intl.Locale.prototype.getCollations().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getCollations, "name", {
+ value: "getCollations",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array-values.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array-values.js
new file mode 100644
index 0000000000..38abb00f09
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array-values.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.collations
+description: >
+ Checks that the return value of Intl.Locale.prototype.collations is an Array
+ that does not contain invalid values.
+info: |
+ CollationsOfLocale ( loc )
+ ...
+ 4. Let list be a List of 1 or more unique collation identifiers, which must
+ be lower case String values conforming to the type sequence from UTS 35
+ Unicode Locale Identifier, section 3.2, sorted in descending preference of
+ those in common use for string comparison in locale. The values "standard"
+ and "search" must be excluded from list.
+features: [Intl.Locale, Intl.Locale-info, Array.prototype.includes]
+---*/
+
+const output = new Intl.Locale('en').getCollations();
+assert(output.length > 0, 'array has at least one element');
+output.forEach(c => {
+ if(['standard', 'search'].includes(c))
+ throw new Test262Error();
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array.js
new file mode 100644
index 0000000000..d4eb1e47ba
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/output-array.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getCollations
+description: >
+ Checks that the return value of Intl.Locale.prototype.getCollations is an Array.
+info: |
+ CollationsOfLocale ( loc )
+ ...
+ 5. Return ! CreateArrayFromListAndPreferred( list, preferred ).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert(Array.isArray(new Intl.Locale('en').getCollations()));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/prop-desc.js
new file mode 100644
index 0000000000..fd48770432
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getCollations
+description: Checks the "getCollations" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getCollations ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getCollations,
+ "function",
+ "typeof Intl.Locale.prototype.getCollations is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getCollations", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getCollations/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getCollations/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getCollations/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/branding.js
new file mode 100644
index 0000000000..ccbf1db7eb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getHourCycles
+description: Verifies the branding check for the "getHourCycles" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getHourCycles ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getHourCycles = Intl.Locale.prototype.getHourCycles;
+
+assert.sameValue(typeof getHourCycles, "function");
+
+assert.throws(TypeError, () => getHourCycles.call(undefined), "undefined");
+assert.throws(TypeError, () => getHourCycles.call(null), "null");
+assert.throws(TypeError, () => getHourCycles.call(true), "true");
+assert.throws(TypeError, () => getHourCycles.call(""), "empty string");
+assert.throws(TypeError, () => getHourCycles.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getHourCycles.call(1), "1");
+assert.throws(TypeError, () => getHourCycles.call({}), "plain object");
+assert.throws(TypeError, () => getHourCycles.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getHourCycles.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/name.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/name.js
new file mode 100644
index 0000000000..b81484eab4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getHourCycles
+description: Checks the "name" property of Intl.Locale.prototype.getHourCycles().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getHourCycles, "name", {
+ value: "getHourCycles",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array-values.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array-values.js
new file mode 100644
index 0000000000..037e31b48c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array-values.js
@@ -0,0 +1,27 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getHourCycles
+description: >
+ Checks that the return value of Intl.Locale.prototype.getHourCycles is an Array
+ that only contains valid values.
+info: |
+ HourCyclesOfLocale ( loc )
+ ...
+ 4. Let list be a List of 1 or more unique hour cycle identifiers, which must
+ be lower case String values indicating either the 12-hour format ("h11",
+ "h12") or the 24-hour format ("h23", "h24"), sorted in descending preference
+ of those in common use for date and time formatting in locale.
+features: [Intl.Locale, Intl.Locale-info, Array.prototype.includes]
+---*/
+
+const output = new Intl.Locale('en').getHourCycles();
+assert(output.length > 0, 'array has at least one element');
+output.forEach(hc => {
+ if(!['h11', 'h12', 'h23', 'h24'].includes(hc))
+ throw new Test262Error();
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array.js
new file mode 100644
index 0000000000..57de2b2430
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/output-array.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getHourCycles
+description: >
+ Checks that the return value of Intl.Locale.prototype.getHourCycles is an Array.
+info: |
+ HourCyclesOfLocale ( loc )
+ ...
+ 5. Return ! CreateArrayFromListAndPreferred( list, preferred ).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert(Array.isArray(new Intl.Locale('en').getHourCycles()));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/prop-desc.js
new file mode 100644
index 0000000000..6133a757f8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getHourCycles
+description: Checks the "getHourCycles" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getHourCycles ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getHourCycles,
+ "function",
+ "typeof Intl.Locale.prototype.getHourCycles is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getHourCycles", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getHourCycles/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/branding.js
new file mode 100644
index 0000000000..4778789f97
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getNumberingSystems
+description: Verifies the branding check for the "getNumberingSystems" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getNumberingSystems ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getNumberingSystems = Intl.Locale.prototype.getNumberingSystems;
+
+assert.sameValue(typeof getNumberingSystems, "function");
+
+assert.throws(TypeError, () => getNumberingSystems.call(undefined), "undefined");
+assert.throws(TypeError, () => getNumberingSystems.call(null), "null");
+assert.throws(TypeError, () => getNumberingSystems.call(true), "true");
+assert.throws(TypeError, () => getNumberingSystems.call(""), "empty string");
+assert.throws(TypeError, () => getNumberingSystems.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getNumberingSystems.call(1), "1");
+assert.throws(TypeError, () => getNumberingSystems.call({}), "plain object");
+assert.throws(TypeError, () => getNumberingSystems.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getNumberingSystems.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/name.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/name.js
new file mode 100644
index 0000000000..2bf49533e9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getNumberingSystems
+description: Checks the "name" property of Intl.Locale.prototype.getNumberingSystems().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getNumberingSystems, "name", {
+ value: "getNumberingSystems",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/output-array.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/output-array.js
new file mode 100644
index 0000000000..399f80a090
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/output-array.js
@@ -0,0 +1,19 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getNumberingSystems
+description: >
+ Checks that the return value of Intl.Locale.prototype.getNumberingSystems is an Array.
+info: |
+ NumberingSystemsOfLocale ( loc )
+ ...
+ 5. Return ! CreateArrayFromListAndPreferred( list, preferred ).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert(Array.isArray(new Intl.Locale('en').getNumberingSystems()));
+assert(new Intl.Locale('en').getNumberingSystems().length > 0, 'array has at least one element');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/prop-desc.js
new file mode 100644
index 0000000000..ddc947d634
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getNumberingSystems
+description: Checks the "getNumberingSystems" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getNumberingSystems ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getNumberingSystems,
+ "function",
+ "typeof Intl.Locale.prototype.getNumberingSystems is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getNumberingSystems", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getNumberingSystems/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/branding.js
new file mode 100644
index 0000000000..c8516ebb09
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTextInfo
+description: Verifies the branding check for the "getTextInfo" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getTextInfo ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getTextInfo = Intl.Locale.prototype.getTextInfo;
+
+assert.sameValue(typeof getTextInfo, "function");
+
+assert.throws(TypeError, () => getTextInfo.call(undefined), "undefined");
+assert.throws(TypeError, () => getTextInfo.call(null), "null");
+assert.throws(TypeError, () => getTextInfo.call(true), "true");
+assert.throws(TypeError, () => getTextInfo.call(""), "empty string");
+assert.throws(TypeError, () => getTextInfo.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getTextInfo.call(1), "1");
+assert.throws(TypeError, () => getTextInfo.call({}), "plain object");
+assert.throws(TypeError, () => getTextInfo.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getTextInfo.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/name.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/name.js
new file mode 100644
index 0000000000..5fc3d258d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTextInfo
+description: Checks the "name" property of Intl.Locale.prototype.getTextInfo().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getTextInfo, "name", {
+ value: "getTextInfo",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object-keys.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object-keys.js
new file mode 100644
index 0000000000..3fbb36a4d0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object-keys.js
@@ -0,0 +1,34 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getTextInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getTextInfo is an Object
+ with the correct keys and properties.
+info: |
+ get Intl.Locale.prototype.getTextInfo
+ ...
+ 7. Perform ! CreateDataPropertyOrThrow(info, "direction", dir).
+features: [Intl.Locale,Intl.Locale-info]
+includes: [propertyHelper.js, compareArray.js]
+---*/
+
+const result = new Intl.Locale('en').getTextInfo();
+
+assert.compareArray(Reflect.ownKeys(result), ['direction']);
+
+verifyProperty(result, 'direction', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+const direction = new Intl.Locale('en').getTextInfo().direction;
+assert(
+ direction === 'rtl' || direction === 'ltr',
+ 'value of the `direction` property'
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object.js
new file mode 100644
index 0000000000..7032f6155f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/output-object.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getTextInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getTextInfo is an Object.
+info: |
+ get Intl.Locale.prototype.getTextInfo
+ ...
+ 5. Let info be ! ObjectCreate(%Object.prototype%).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(Object.getPrototypeOf(new Intl.Locale('en').getTextInfo()), Object.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/prop-desc.js
new file mode 100644
index 0000000000..ae170b4646
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTextInfo
+description: Checks the "getTextInfo" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getTextInfo ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getTextInfo,
+ "function",
+ "typeof Intl.Locale.prototype.getTextInfo is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getTextInfo", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTextInfo/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/branding.js
new file mode 100644
index 0000000000..4305010e18
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTimeZones
+description: Verifies the branding check for the "getTimeZones" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getTimeZones ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getTimeZones = Intl.Locale.prototype.getTimeZones;
+
+assert.sameValue(typeof getTimeZones, "function");
+
+assert.throws(TypeError, () => getTimeZones.call(undefined), "undefined");
+assert.throws(TypeError, () => getTimeZones.call(null), "null");
+assert.throws(TypeError, () => getTimeZones.call(true), "true");
+assert.throws(TypeError, () => getTimeZones.call(""), "empty string");
+assert.throws(TypeError, () => getTimeZones.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getTimeZones.call(1), "1");
+assert.throws(TypeError, () => getTimeZones.call({}), "plain object");
+assert.throws(TypeError, () => getTimeZones.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getTimeZones.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/name.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/name.js
new file mode 100644
index 0000000000..0af95e77a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTimeZones
+description: Checks the "name" property of Intl.Locale.prototype.getTimeZones().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getTimeZones, "name", {
+ value: "getTimeZones",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-sorted.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-sorted.js
new file mode 100644
index 0000000000..f492683d50
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-sorted.js
@@ -0,0 +1,25 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getTimeZones
+description: >
+ Checks that the return value of Intl.Locale.prototype.getTimeZones is a sorted
+ Array.
+info: |
+ TimeZonesOfLocale ( loc )
+ ...
+ 4. Let list be a List of 1 or more unique time zone identifiers, which must be
+ String values indicating a Zone or Link name of the IANA Time Zone Database,
+ ordered as if an Array of the same values had been sorted using
+ %Array.prototype.sort% using undefined as comparefn, of those in common use
+ in region.
+features: [Intl.Locale,Intl.Locale-info]
+includes: [compareArray.js]
+---*/
+
+const output = new Intl.Locale('en-US').getTimeZones();
+assert.compareArray(output, [...output].sort());
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-undefined.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-undefined.js
new file mode 100644
index 0000000000..3de4ce7cd9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array-undefined.js
@@ -0,0 +1,20 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getTimeZones
+description: >
+ Checks that the return value of Intl.Locale.prototype.timeZones is undefined
+ when no region subtag is used.
+info: |
+ get Intl.Locale.prototype.timeZones
+ ...
+ 4. If the unicode_language_id production of locale does not contain the
+ ["-" unicode_region_subtag] sequence, return undefined.
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(new Intl.Locale('en').getTimeZones(), undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array.js
new file mode 100644
index 0000000000..4cc3121096
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/output-array.js
@@ -0,0 +1,20 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getTimeZones
+description: >
+ Checks that the return value of Intl.Locale.prototype.getTimeZones is an Array
+ when a region subtag is used.
+info: |
+ TimeZonesOfLocale ( loc )
+ ...
+ 5. Return ! CreateArrayFromListAndPreferred( list, preferred ).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert(Array.isArray(new Intl.Locale('en-US').getTimeZones()));
+assert(new Intl.Locale('en-US').getTimeZones().length > 0, 'array has at least one element');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/prop-desc.js
new file mode 100644
index 0000000000..0dff234d1e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getTimeZones
+description: Checks the "getTimeZones" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getTimeZones ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getTimeZones,
+ "function",
+ "typeof Intl.Locale.prototype.getTimeZones is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getTimeZones", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getTimeZones/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/branding.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/branding.js
new file mode 100644
index 0000000000..de77d1d1a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/branding.js
@@ -0,0 +1,30 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getWeekInfo
+description: Verifies the branding check for the "getWeekInfo" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getWeekInfo ()
+
+ 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
+
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+const getWeekInfo = Intl.Locale.prototype.getWeekInfo;
+
+assert.sameValue(typeof getWeekInfo, "function");
+
+assert.throws(TypeError, () => getWeekInfo.call(undefined), "undefined");
+assert.throws(TypeError, () => getWeekInfo.call(null), "null");
+assert.throws(TypeError, () => getWeekInfo.call(true), "true");
+assert.throws(TypeError, () => getWeekInfo.call(""), "empty string");
+assert.throws(TypeError, () => getWeekInfo.call(Symbol()), "symbol");
+assert.throws(TypeError, () => getWeekInfo.call(1), "1");
+assert.throws(TypeError, () => getWeekInfo.call({}), "plain object");
+assert.throws(TypeError, () => getWeekInfo.call(Intl.Locale), "Intl.Locale");
+assert.throws(TypeError, () => getWeekInfo.call(Intl.Locale.prototype), "Intl.Locale.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/browser.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-id.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-id.js
new file mode 100644
index 0000000000..9eedea5b8c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-id.js
@@ -0,0 +1,34 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getWeekInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getWeekInfo is an Object
+ with the correct keys and properties.
+info: |
+ get Intl.Locale.prototype.getWeekInfo
+ ...
+ 6. Perform ! CreateDataPropertyOrThrow(info, "firstDay", wi.[[FirstDay]]).
+features: [Reflect,Intl.Locale,Intl.Locale-info]
+---*/
+
+const validFirstDayOfWeekIds = [
+ ["en-u-fw-mon", 1],
+ ["en-u-fw-tue", 2],
+ ["en-u-fw-wed", 3],
+ ["en-u-fw-thu", 4],
+ ["en-u-fw-fri", 5],
+ ["en-u-fw-sat", 6],
+ ["en-u-fw-sun", 7],
+];
+for (const [id, expected] of validFirstDayOfWeekIds) {
+ assert.sameValue(
+ new Intl.Locale(id).getWeekInfo().firstDay,
+ expected,
+ `new Intl.Locale(${id}).getWeekInfo().firstDay returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-option.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-option.js
new file mode 100644
index 0000000000..9b0619c810
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/firstDay-by-option.js
@@ -0,0 +1,55 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getWeekInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getWeekInfo is an Object
+ with the correct keys and properties.
+info: |
+ get Intl.Locale.prototype.getWeekInfo
+ ...
+ 6. Perform ! CreateDataPropertyOrThrow(info, "firstDay", wi.[[FirstDay]]).
+features: [Reflect,Intl.Locale,Intl.Locale-info]
+---*/
+
+const validFirstDayOfWeekOptions = [
+ ["mon", 1],
+ ["tue", 2],
+ ["wed", 3],
+ ["thu", 4],
+ ["fri", 5],
+ ["sat", 6],
+ ["sun", 7],
+ ["1", 1],
+ ["2", 2],
+ ["3", 3],
+ ["4", 4],
+ ["5", 5],
+ ["6", 6],
+ ["7", 7],
+ ["0", 7],
+ [1, 1],
+ [2, 2],
+ [3, 3],
+ [4, 4],
+ [5, 5],
+ [6, 6],
+ [7, 7],
+ [0, 7],
+];
+for (const [firstDayOfWeek, expected] of validFirstDayOfWeekOptions) {
+ assert.sameValue(
+ new Intl.Locale('en', { firstDayOfWeek }).getWeekInfo().firstDay,
+ expected,
+ `new Intl.Locale("en", { firstDayOfWeek: ${firstDayOfWeek} }).getWeekInfo().firstDay returns "${expected}"`
+ );
+ assert.sameValue(
+ new Intl.Locale('en-u-fw-WED', { firstDayOfWeek }).getWeekInfo().firstDay,
+ expected,
+ `new Intl.Locale("en-u-fw-WED", { firstDayOfWeek: ${firstDayOfWeek} }).firstDayOfWeek returns "${expected}"`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/name.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/name.js
new file mode 100644
index 0000000000..b834ac2749
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getWeekInfo
+description: Checks the "name" property of Intl.Locale.prototype.getWeekInfo().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+verifyProperty(Intl.Locale.prototype.getWeekInfo, "name", {
+ value: "getWeekInfo",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js
new file mode 100644
index 0000000000..0d3da38f07
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object-keys.js
@@ -0,0 +1,67 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// Copyright 2021 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getWeekInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getWeekInfo is an Object
+ with the correct keys and properties.
+info: |
+ get Intl.Locale.prototype.getWeekInfo
+ ...
+ 6. Perform ! CreateDataPropertyOrThrow(info, "firstDay", wi.[[FirstDay]]).
+ 7. Perform ! CreateDataPropertyOrThrow(info, "weekend", we).
+ 8. Perform ! CreateDataPropertyOrThrow(info, "minimalDays", wi.[[MinimalDays]]).
+ ...
+ CreateDataProperty ( O, P, V )
+ ...
+ 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true,
+ [[Enumerable]]: true, [[Configurable]]: true }.
+features: [Reflect,Intl.Locale,Intl.Locale-info]
+includes: [propertyHelper.js, compareArray.js]
+---*/
+
+const result = new Intl.Locale('en').getWeekInfo();
+function isIntegerBetweenOneAndSeven(value) {
+ return value === 1 || value === 2 || value === 3 || value === 4 || value === 5 || value === 6 || value === 7;
+}
+
+assert.compareArray(Reflect.ownKeys(result), ['firstDay', 'weekend', 'minimalDays']);
+
+verifyProperty(result, 'firstDay', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+assert(
+ isIntegerBetweenOneAndSeven(new Intl.Locale('en').getWeekInfo().firstDay),
+ '`firstDay` must be an integer between one and seven (inclusive)'
+);
+
+verifyProperty(result, 'weekend', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+assert(
+ new Intl.Locale('en').getWeekInfo().weekend.every(isIntegerBetweenOneAndSeven),
+ '`weekend` must include integers between one and seven (inclusive)'
+);
+
+let original = new Intl.Locale('en').getWeekInfo().weekend;
+let sorted = original.slice().sort();
+assert.compareArray(original, sorted);
+
+verifyProperty(result, 'minimalDays', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+assert(
+ isIntegerBetweenOneAndSeven(new Intl.Locale('en').getWeekInfo().minimalDays),
+ '`minimalDays` must be an integer between one and seven (inclusive)'
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object.js
new file mode 100644
index 0000000000..b799a3db78
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/output-object.js
@@ -0,0 +1,18 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.getWeekInfo
+description: >
+ Checks that the return value of Intl.Locale.prototype.getWeekInfo is an Object.
+info: |
+ get Intl.Locale.prototype.getWeekInfo
+ ...
+ 5. Let info be ! ObjectCreate(%Object.prototype%).
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(Object.getPrototypeOf(new Intl.Locale('en').getWeekInfo()), Object.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/prop-desc.js
new file mode 100644
index 0000000000..2ed856a03d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/prop-desc.js
@@ -0,0 +1,28 @@
+// |reftest| skip -- Intl.Locale-info is not supported
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.getWeekInfo
+description: Checks the "getWeekInfo" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.getWeekInfo ()
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale,Intl.Locale-info]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.getWeekInfo,
+ "function",
+ "typeof Intl.Locale.prototype.getWeekInfo is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "getWeekInfo", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/shell.js b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/getWeekInfo/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/hourCycle/branding.js b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/branding.js
new file mode 100644
index 0000000000..1716b0ff33
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.hourCycle
+description: >
+ Verifies the branding check for the "hourCycle" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.hourCycle
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "hourCycle");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/hourCycle/browser.js b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/hourCycle/name.js b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/name.js
new file mode 100644
index 0000000000..b40e2c6895
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.hourCycle
+description: >
+ Checks the "name" property of Intl.Locale.prototype.hourCycle.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "hourCycle").get;
+verifyProperty(getter, "name", {
+ value: "get hourCycle",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/hourCycle/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/prop-desc.js
new file mode 100644
index 0000000000..1f96c725b8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "hourCycle" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.hourCycle
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "hourCycle");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "hourCycle", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/hourCycle/shell.js b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/hourCycle/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/language/branding.js b/js/src/tests/test262/intl402/Locale/prototype/language/branding.js
new file mode 100644
index 0000000000..b9da32ac87
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/language/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.language
+description: >
+ Verifies the branding check for the "language" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.language
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "language");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/language/browser.js b/js/src/tests/test262/intl402/Locale/prototype/language/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/language/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/language/name.js b/js/src/tests/test262/intl402/Locale/prototype/language/name.js
new file mode 100644
index 0000000000..089953cbda
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/language/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.language
+description: >
+ Checks the "name" property of Intl.Locale.prototype.language.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "language").get;
+verifyProperty(getter, "name", {
+ value: "get language",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/language/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/language/prop-desc.js
new file mode 100644
index 0000000000..d2efe7ffa4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/language/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "language" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.language
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "language");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "language", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/language/shell.js b/js/src/tests/test262/intl402/Locale/prototype/language/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/language/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/branding.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/branding.js
new file mode 100644
index 0000000000..38dbb77c52
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/branding.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.maximize
+description: >
+ Verifies the branding check for the "maximize" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.maximize
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const maximize = Intl.Locale.prototype.maximize;
+
+assert.sameValue(typeof maximize, "function");
+
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => maximize.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/browser.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/length.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/length.js
new file mode 100644
index 0000000000..add94c6696
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.maximize
+description: >
+ Checks the "length" property of Intl.Locale.prototype.maximize().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Locale constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale.prototype.maximize, 'length', {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/name.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/name.js
new file mode 100644
index 0000000000..259f91a34c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.maximize
+description: >
+ Checks the "name" property of Intl.Locale.prototype.maximize().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale.prototype.maximize, 'name', {
+ value: 'maximize',
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/prop-desc.js
new file mode 100644
index 0000000000..990e8d4455
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/prop-desc.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.maximize
+description: >
+ Checks the "maximize" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.maximize ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.maximize,
+ 'function',
+ 'typeof Intl.Locale.prototype.maximize is function'
+);
+
+verifyProperty(Intl.Locale.prototype, 'maximize', {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/maximize/shell.js b/js/src/tests/test262/intl402/Locale/prototype/maximize/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/maximize/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/branding.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/branding.js
new file mode 100644
index 0000000000..c86f491323
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/branding.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.minimize
+description: >
+ Verifies the branding check for the "minimize" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.minimize
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const minimize = Intl.Locale.prototype.minimize;
+
+assert.sameValue(typeof minimize, "function");
+
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => minimize.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/browser.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/length.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/length.js
new file mode 100644
index 0000000000..ad329cd3be
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.minimize
+description: >
+ Checks the "length" property of Intl.Locale.prototype.minimize().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Locale constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale.prototype.minimize, 'length', {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/name.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/name.js
new file mode 100644
index 0000000000..5a67ae8956
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.minimize
+description: >
+ Checks the "name" property of Intl.Locale.prototype.minimize().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale.prototype.minimize, 'name', {
+ value: 'minimize',
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/prop-desc.js
new file mode 100644
index 0000000000..f389b23797
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "minimize" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.minimize ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.minimize,
+ 'function',
+ 'typeof Intl.Locale.prototype.minimize is function'
+);
+
+verifyProperty(Intl.Locale.prototype, 'minimize', {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/removing-likely-subtags-first-adds-likely-subtags.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/removing-likely-subtags-first-adds-likely-subtags.js
new file mode 100644
index 0000000000..7ee1c4ba49
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/removing-likely-subtags-first-adds-likely-subtags.js
@@ -0,0 +1,51 @@
+// Copyright 2020 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.minimize
+description: >
+ The "Remove Likely Subtags" algorithm adds likely subtags before processing the locale.
+info: |
+ Intl.Locale.prototype.minimize ()
+ 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
+ If an error is signaled, set minimal to loc.[[Locale]].
+
+ UTS 35, §4.3 Likely Subtags
+ Remove Likely Subtags
+
+ 1. First get max = AddLikelySubtags(inputLocale). If an error is signaled, return it.
+ 2. ...
+features: [Intl.Locale]
+---*/
+
+var testDataMinimal = {
+ // Undefined primary language.
+ "und": "en",
+ "und-Thai": "th",
+ "und-419": "es-419",
+ "und-150": "en-150",
+ "und-AT": "de-AT",
+
+ // https://unicode-org.atlassian.net/browse/ICU-13786
+ "aae-Latn-IT": "aae",
+ "aae-Thai-CO": "aae-Thai-CO",
+
+ // https://unicode-org.atlassian.net/browse/ICU-10220
+ // https://unicode-org.atlassian.net/browse/ICU-12345
+ "und-CW": "pap",
+ "und-US": "en",
+ "zh-Hant": "zh-TW",
+ "zh-Hani": "zh-Hani",
+};
+
+for (const [tag, minimal] of Object.entries(testDataMinimal)) {
+ // Assert the |minimal| tag is indeed minimal.
+ assert.sameValue(new Intl.Locale(minimal).minimize().toString(), minimal,
+ `"${minimal}" should be minimal`);
+
+ // Assert RemoveLikelySubtags(tag) returns |minimal|.
+ assert.sameValue(new Intl.Locale(tag).minimize().toString(), minimal,
+ `"${tag}".minimize() should be "${minimal}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/minimize/shell.js b/js/src/tests/test262/intl402/Locale/prototype/minimize/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/minimize/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/branding.js b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/branding.js
new file mode 100644
index 0000000000..6b736e9292
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.numberingSystem
+description: >
+ Verifies the branding check for the "numberingSystem" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.numberingSystem
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numberingSystem");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/browser.js b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/name.js b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/name.js
new file mode 100644
index 0000000000..1e0b908694
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.numberingSystem
+description: >
+ Checks the "name" property of Intl.Locale.prototype.numberingSystem.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numberingSystem").get;
+verifyProperty(getter, "name", {
+ value: "get numberingSystem",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/prop-desc.js
new file mode 100644
index 0000000000..4f6f62a7c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "numberingSystem" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.numberingSystem
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numberingSystem");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "numberingSystem", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/shell.js b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numberingSystem/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numeric/branding.js b/js/src/tests/test262/intl402/Locale/prototype/numeric/branding.js
new file mode 100644
index 0000000000..df1354be4e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numeric/branding.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.numeric
+description: >
+ Verifies the branding check for the "numeric" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.numeric
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numeric");
+if (propdesc !== undefined) {
+ const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+ ];
+
+ for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numeric/browser.js b/js/src/tests/test262/intl402/Locale/prototype/numeric/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numeric/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numeric/name.js b/js/src/tests/test262/intl402/Locale/prototype/numeric/name.js
new file mode 100644
index 0000000000..db01e07741
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numeric/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.numeric
+description: >
+ Checks the "name" property of Intl.Locale.prototype.numeric.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numeric").get;
+verifyProperty(getter, "name", {
+ value: "get numeric",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numeric/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/numeric/prop-desc.js
new file mode 100644
index 0000000000..d361b3e5a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numeric/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "numeric" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.numeric
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "numeric");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "numeric", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/numeric/shell.js b/js/src/tests/test262/intl402/Locale/prototype/numeric/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/numeric/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/prop-desc.js
new file mode 100644
index 0000000000..f31eec1c25
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/prop-desc.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype
+description: >
+ Checks the "prototype" property of the Locale constructor.
+info: |
+ Intl.Locale.prototype
+
+ The value of Intl.Locale.prototype is %LocalePrototype%.
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+verifyProperty(Intl.Locale, 'prototype', {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/region/branding.js b/js/src/tests/test262/intl402/Locale/prototype/region/branding.js
new file mode 100644
index 0000000000..47b07ae9e8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/region/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.region
+description: >
+ Verifies the branding check for the "region" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.region
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "region");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/region/browser.js b/js/src/tests/test262/intl402/Locale/prototype/region/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/region/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/region/name.js b/js/src/tests/test262/intl402/Locale/prototype/region/name.js
new file mode 100644
index 0000000000..5aabb93dda
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/region/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.region
+description: >
+ Checks the "name" property of Intl.Locale.prototype.region.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "region").get;
+verifyProperty(getter, "name", {
+ value: "get region",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/region/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/region/prop-desc.js
new file mode 100644
index 0000000000..ca3a753cd7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/region/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "region" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.region
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAregion objects in the ECMAregion 2019 region Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "region");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "region", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/region/shell.js b/js/src/tests/test262/intl402/Locale/prototype/region/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/region/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/script/branding.js b/js/src/tests/test262/intl402/Locale/prototype/script/branding.js
new file mode 100644
index 0000000000..ffa1e8f66d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/script/branding.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.script
+description: >
+ Verifies the branding check for the "script" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.script
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "script");
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => propdesc.get.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/script/browser.js b/js/src/tests/test262/intl402/Locale/prototype/script/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/script/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/script/name.js b/js/src/tests/test262/intl402/Locale/prototype/script/name.js
new file mode 100644
index 0000000000..a36f0fbbea
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/script/name.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype.script
+description: >
+ Checks the "name" property of Intl.Locale.prototype.script.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "script").get;
+verifyProperty(getter, "name", {
+ value: "get script",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/script/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/script/prop-desc.js
new file mode 100644
index 0000000000..3a510e34c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/script/prop-desc.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "script" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.script
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 script Specification, 10th edition, clause 17, or successor.
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "script");
+assert.sameValue(propdesc.set, undefined);
+assert.sameValue(typeof propdesc.get, "function");
+
+verifyProperty(Intl.Locale.prototype, "script", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/script/shell.js b/js/src/tests/test262/intl402/Locale/prototype/script/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/script/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/shell.js b/js/src/tests/test262/intl402/Locale/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toString/branding.js b/js/src/tests/test262/intl402/Locale/prototype/toString/branding.js
new file mode 100644
index 0000000000..8b14ec4fb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toString/branding.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale.prototype.toString
+description: >
+ Verifies the branding check for the "toString" function of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.toString
+
+ 2. If Type(loc) is not Object or loc does not have an [[InitializedLocale]] internal slot, then
+ a. Throw a TypeError exception.
+features: [Intl.Locale]
+---*/
+
+const toString = Intl.Locale.prototype.toString;
+
+assert.sameValue(typeof toString, "function");
+
+const invalidValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Locale.prototype,
+];
+
+for (const invalidValue of invalidValues) {
+ assert.throws(TypeError, () => toString.call(invalidValue));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toString/browser.js b/js/src/tests/test262/intl402/Locale/prototype/toString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toString/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toString/prop-desc.js b/js/src/tests/test262/intl402/Locale/prototype/toString/prop-desc.js
new file mode 100644
index 0000000000..5ab7eaeac4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toString/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale
+description: >
+ Checks the "toString" property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype.toString ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(
+ typeof Intl.Locale.prototype.toString,
+ "function",
+ "typeof Intl.Locale.prototype.toString is function"
+);
+
+verifyProperty(Intl.Locale.prototype, "toString", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toString/shell.js b/js/src/tests/test262/intl402/Locale/prototype/toString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toString/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString-removed-tag.js b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString-removed-tag.js
new file mode 100644
index 0000000000..116caedeb7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString-removed-tag.js
@@ -0,0 +1,20 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype-@@tostringtag
+description: >
+ Checks Object.prototype.toString with Intl.Locale objects.
+info: |
+ Intl.Locale.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.Locale".
+features: [Intl.Locale, Symbol.toStringTag]
+---*/
+
+delete Intl.Locale.prototype[Symbol.toStringTag];
+
+assert.sameValue(Object.prototype.toString.call(Intl.Locale.prototype), "[object Object]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Locale("en")), "[object Object]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..42896406e3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toString.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype-@@tostringtag
+description: >
+ Checks Object.prototype.toString with Intl.Locale objects.
+info: |
+ Intl.Locale.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.Locale".
+features: [Intl.Locale, Symbol.toStringTag]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Locale.prototype), "[object Intl.Locale]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Locale("en")), "[object Intl.Locale]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..d58a240de7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.locale.prototype-@@tostringtag
+description: >
+ Checks the @@toStringTag property of the Locale prototype object.
+info: |
+ Intl.Locale.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.Locale".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Locale, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.Locale.prototype, Symbol.toStringTag, {
+ value: 'Intl.Locale',
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/reject-duplicate-variants-in-tlang.js b/js/src/tests/test262/intl402/Locale/reject-duplicate-variants-in-tlang.js
new file mode 100644
index 0000000000..c2d01bc0b9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/reject-duplicate-variants-in-tlang.js
@@ -0,0 +1,52 @@
+// Copyright 2020 Jeff Walden, Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-isstructurallyvalidlanguagetag
+description: >
+ Verifies that just as duplicate variants in a tag ("en-emodeng-emodeng") make
+ the tag structurally invalid, so too do duplicate variants in the tlang
+ component of an otherwise structurally valid tag ("de-t-emodeng-emodeng"),
+ make it structurally invalid.
+info: |
+ if a `transformed_extensions` component that contains a `tlang` component is
+ present, then
+ the `tlang` component contains no duplicate `unicode_variant_subtag`
+ subtags.
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+function mustReject(tag) {
+ assert.throws(RangeError, () => {
+ // Direct matches are rejected.
+ new Intl.Locale(tag);
+ }, `tag "${tag}" must be considered structurally invalid`);
+}
+
+// BCP47 since forever, and ECMA-402 as consequence, do not consider tags that
+// contain duplicate variants to be structurally valid. This restriction also
+// applies within the |tlang| component (indicating the source locale from which
+// relevant content was transformed) of a broader language tag.
+
+// Direct matches are rejected.
+mustReject("de-t-en-emodeng-emodeng");
+// Case-insensitive matches are also rejected.
+mustReject("de-t-en-Emodeng-emodeng");
+// ...and in either order.
+mustReject("de-t-en-emodeng-Emodeng");
+
+// Repeat the above tests with additional variants interspersed at each point
+// for completeness.
+mustReject("de-t-en-variant-emodeng-emodeng");
+mustReject("de-t-en-variant-Emodeng-emodeng");
+mustReject("de-t-en-variant-emodeng-Emodeng");
+mustReject("de-t-en-emodeng-variant-emodeng");
+mustReject("de-t-en-Emodeng-variant-emodeng");
+mustReject("de-t-en-emodeng-variant-Emodeng");
+mustReject("de-t-en-emodeng-emodeng-variant");
+mustReject("de-t-en-Emodeng-emodeng-variant");
+mustReject("de-t-en-emodeng-Emodeng-variant");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/reject-duplicate-variants.js b/js/src/tests/test262/intl402/Locale/reject-duplicate-variants.js
new file mode 100644
index 0000000000..cb6025388f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/reject-duplicate-variants.js
@@ -0,0 +1,46 @@
+// Copyright 2020 Jeff Walden, Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-isstructurallyvalidlanguagetag
+description: >
+ Verifies that duplicate variants in a tag ("en-emodeng-emodeng") make the tag
+ structurally invalid.
+info: |
+ the `unicode_language_id` within _locale_ contains no duplicate
+ `unicode_variant_subtag` subtags
+features: [Intl.Locale]
+---*/
+
+assert.sameValue(typeof Intl.Locale, "function");
+
+function mustReject(tag) {
+ assert.throws(RangeError, () => {
+ // Direct matches are rejected.
+ new Intl.Locale(tag);
+ }, `tag "${tag}" must be considered structurally invalid`);
+}
+
+// BCP47 since forever, and ECMA-402 as consequence, do not consider tags that
+// contain duplicate variants to be structurally valid.
+
+// Direct matches are rejected.
+mustReject("en-emodeng-emodeng");
+// Case-insensitive matches are also rejected.
+mustReject("en-Emodeng-emodeng");
+// ...and in either order.
+mustReject("en-emodeng-Emodeng");
+
+// Repeat the above tests with additional variants interspersed at each point
+// for completeness.
+mustReject("en-variant-emodeng-emodeng");
+mustReject("en-variant-Emodeng-emodeng");
+mustReject("en-variant-emodeng-Emodeng");
+mustReject("en-emodeng-variant-emodeng");
+mustReject("en-Emodeng-variant-emodeng");
+mustReject("en-emodeng-variant-Emodeng");
+mustReject("en-emodeng-emodeng-variant");
+mustReject("en-Emodeng-emodeng-variant");
+mustReject("en-emodeng-Emodeng-variant");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Locale/shell.js b/js/src/tests/test262/intl402/Locale/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/shell.js
diff --git a/js/src/tests/test262/intl402/Locale/subclassing.js b/js/src/tests/test262/intl402/Locale/subclassing.js
new file mode 100644
index 0000000000..4afc3a5a83
--- /dev/null
+++ b/js/src/tests/test262/intl402/Locale/subclassing.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Locale
+description: Checks that Locale can be subclassed.
+info: |
+ Intl.Locale( tag [, options] )
+
+ 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, %LocalePrototype%, internalSlotsList).
+
+features: [Intl.Locale]
+---*/
+
+class CustomLocale extends Intl.Locale {
+ constructor(locales, options) {
+ super(locales, options);
+ this.isCustom = true;
+ }
+}
+
+var locale = new CustomLocale("de");
+assert.sameValue(locale.isCustom, true, "Custom property");
+assert.sameValue(locale.toString(), "de", "Direct call");
+assert.sameValue(Intl.Locale.prototype.toString.call(locale), "de", "Indirect call");
+assert.sameValue(Object.getPrototypeOf(locale), CustomLocale.prototype, "Prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/browser.js b/js/src/tests/test262/intl402/Number/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/browser.js
diff --git a/js/src/tests/test262/intl402/Number/prototype/browser.js b/js/src/tests/test262/intl402/Number/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Number/prototype/shell.js b/js/src/tests/test262/intl402/Number/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/builtin.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/builtin.js
new file mode 100644
index 0000000000..cc3639f018
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.2.1_L15
+description: >
+ Tests that Number.prototype.toLocaleString meets the requirements
+ for built-in objects defined by the introduction of chapter 17 of
+ the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Number.prototype.toLocaleString), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Number.prototype.toLocaleString),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Number.prototype.toLocaleString), Function.prototype);
+
+assert.sameValue(Number.prototype.toLocaleString.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Number.prototype.toLocaleString), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/default-options-object-prototype.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/default-options-object-prototype.js
new file mode 100644
index 0000000000..d6d1f0693c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/default-options-object-prototype.js
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for NumberFormat as a null prototype is used.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+---*/
+
+if (new Intl.NumberFormat("en").resolvedOptions().locale === "en") {
+ Object.prototype.maximumFractionDigits = 1;
+ assert.sameValue(1.23.toLocaleString("en"), "1.23");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/length.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/length.js
new file mode 100644
index 0000000000..53286f8b64
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-number.prototype.tolocalestring
+description: >
+ Number.prototype.toLocaleString.length is 0.
+info: |
+ Number.prototype.toLocaleString ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Number.prototype.toLocaleString, 'length', {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/returns-same-results-as-NumberFormat.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/returns-same-results-as-NumberFormat.js
new file mode 100644
index 0000000000..10c54d530b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/returns-same-results-as-NumberFormat.js
@@ -0,0 +1,40 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.2.1_5
+description: >
+ Tests that Number.prototype.toLocaleString produces the same
+ results as Intl.NumberFormat.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+var numbers = [0, -0, 1, -1, 5.5, 123, -123, -123.45, 123.44501, 0.001234,
+ -0.00000000123, 0.00000000000000000000000000000123, 1.2, 0.0000000012344501,
+ 123445.01, 12344501000000000000000000000000000, -12344501000000000000000000000000000,
+ Infinity, -Infinity, NaN];
+var locales = [undefined, ["de"], ["th-u-nu-thai"], ["en"], ["ja-u-nu-jpanfin"], ["ar-u-nu-arab"]];
+var options = [
+ undefined,
+ {style: "percent"},
+ {style: "currency", currency: "EUR", currencyDisplay: "symbol"},
+ {style: "currency", currency: "IQD", currencyDisplay: "symbol"},
+ {style: "currency", currency: "KMF", currencyDisplay: "symbol"},
+ {style: "currency", currency: "CLF", currencyDisplay: "symbol"},
+ {useGrouping: false, minimumIntegerDigits: 3, minimumFractionDigits: 1, maximumFractionDigits: 3}
+];
+
+locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var referenceNumberFormat = new Intl.NumberFormat(locales, options);
+ var referenceFormatted = numbers.map(referenceNumberFormat.format);
+
+ var formatted = numbers.map(function (a) { return a.toLocaleString(locales, options); });
+ assert.compareArray(formatted, referenceFormatted,
+ "(Testing with locales " + locales + "; options " +
+ (options ? JSON.stringify(options) : options) + ".)");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/taint-Intl-NumberFormat.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/taint-Intl-NumberFormat.js
new file mode 100644
index 0000000000..ba9a755568
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/taint-Intl-NumberFormat.js
@@ -0,0 +1,16 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.2.1_4_2
+description: >
+ Tests that Number.prototype.toLocaleString uses the standard
+ built-in Intl.NumberFormat constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Intl, "NumberFormat");
+(0.0).toLocaleString();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/this-number-value.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/this-number-value.js
new file mode 100644
index 0000000000..86e68ef2ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/this-number-value.js
@@ -0,0 +1,28 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.2.1_1
+description: Tests that toLocaleString handles "this Number value" correctly.
+author: Norbert Lindenberg
+---*/
+
+var invalidValues = [undefined, null, "5", false, {valueOf: function () { return 5; }}];
+var validValues = [5, NaN, -1234567.89, -Infinity];
+
+invalidValues.forEach(function (value) {
+ assert.throws(TypeError, function() {
+ var result = Number.prototype.toLocaleString.call(value);
+ }, "Number.prototype.toLocaleString did not reject this = " + value + ".");
+});
+
+// for valid values, just check that a Number value and the corresponding
+// Number object get the same result.
+validValues.forEach(function (value) {
+ var Constructor = Number; // to keep jshint happy
+ var valueResult = Number.prototype.toLocaleString.call(value);
+ var objectResult = Number.prototype.toLocaleString.call(new Constructor(value));
+ assert.sameValue(valueResult, objectResult, "Number.prototype.toLocaleString produces different results for Number value " + value + " and corresponding Number object.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js
new file mode 100644
index 0000000000..8dbb5b72a3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/prototype/toLocaleString/throws-same-exceptions-as-NumberFormat.js
@@ -0,0 +1,49 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.2.1_4_1
+description: >
+ Tests that Number.prototype.toLocaleString throws the same
+ exceptions as Intl.NumberFormat.
+author: Norbert Lindenberg
+---*/
+
+var locales = [null, [NaN], ["i"], ["de_DE"]];
+var options = [
+ {localeMatcher: null},
+ {style: "invalid"},
+ {style: "currency"},
+ {style: "currency", currency: "ßP"},
+ {maximumSignificantDigits: -Infinity}
+];
+
+locales.forEach(function (locales) {
+ var referenceError, error;
+ try {
+ var format = new Intl.NumberFormat(locales);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.NumberFormat for locales " + locales + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = (0).toLocaleString(locales);
+ }, "Number.prototype.toLocaleString didn't throw exception for locales " + locales + ".");
+});
+
+options.forEach(function (options) {
+ var referenceError, error;
+ try {
+ var format = new Intl.NumberFormat([], options);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.NumberFormat for options " + JSON.stringify(options) + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = (0).toLocaleString([], options);
+ }, "Number.prototype.toLocaleString didn't throw exception for options " + JSON.stringify(options) + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Number/shell.js b/js/src/tests/test262/intl402/Number/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Number/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/browser.js b/js/src/tests/test262/intl402/NumberFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/builtin.js b/js/src/tests/test262/intl402/NumberFormat/builtin.js
new file mode 100644
index 0000000000..6848ae39e9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/builtin.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.1_L15
+description: >
+ Tests that Intl.NumberFormat meets the requirements for built-in
+ objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.NumberFormat), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.NumberFormat), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.NumberFormat), Function.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/casing-numbering-system-options.js b/js/src/tests/test262/intl402/NumberFormat/casing-numbering-system-options.js
new file mode 100644
index 0000000000..11572923c6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/casing-numbering-system-options.js
@@ -0,0 +1,29 @@
+// Copyright 2020 Google Inc, Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options numberingSystem are mapped to lower case.
+author: Caio Lima
+features: [Array.prototype.includes]
+---*/
+
+let defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
+
+let supportedNumberingSystems = ["latn", "arab"].filter(nu =>
+ new Intl.NumberFormat(defaultLocale + "-u-nu-" + nu)
+ .resolvedOptions().numberingSystem === nu
+);
+
+if (supportedNumberingSystems.includes("latn")) {
+ let numberFormat = new Intl.NumberFormat(defaultLocale + "-u-nu-lATn");
+ assert.sameValue(numberFormat.resolvedOptions().numberingSystem, "latn", "Numbering system option should be in lower case");
+}
+
+if (supportedNumberingSystems.includes("arab")) {
+ let numberFormat = new Intl.NumberFormat(defaultLocale + "-u-nu-Arab");
+ assert.sameValue(numberFormat.resolvedOptions().numberingSystem, "arab", "Numbering system option should be in lower case");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-compact.js b/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-compact.js
new file mode 100644
index 0000000000..ef5061b87d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-compact.js
@@ -0,0 +1,46 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 19. Let compactDisplay be ? GetOption(options, "compactDisplay", "string", « "short", "long" », "short").
+ 20. If notation is "compact", then
+ a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
+
+includes: [compareArray.js]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const values = [
+ [undefined, "short"],
+ ["short"],
+ ["long"],
+];
+
+for (const [value, expected = value] of values) {
+ const callOrder = [];
+ const nf = new Intl.NumberFormat([], {
+ get notation() {
+ callOrder.push("notation");
+ return "compact";
+ },
+ get compactDisplay() {
+ callOrder.push("compactDisplay");
+ return value;
+ }
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("compactDisplay" in resolvedOptions, true);
+ assert.sameValue(resolvedOptions.compactDisplay, expected);
+
+ assert.compareArray(callOrder, [
+ "notation",
+ "compactDisplay",
+ ]);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-no-compact.js b/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-no-compact.js
new file mode 100644
index 0000000000..9f5b1336ad
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-compactDisplay-no-compact.js
@@ -0,0 +1,55 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 19. Let compactDisplay be ? GetOption(options, "compactDisplay", "string", « "short", "long" », "short").
+ 20. If notation is "compact", then
+ a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
+
+includes: [compareArray.js]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const values = [
+ [undefined, "short"],
+ ["short"],
+ ["long"],
+];
+
+const notations = [
+ undefined,
+ "standard",
+ "scientific",
+ "engineering",
+];
+
+for (const notation of notations) {
+ for (const [value, expected = value] of values) {
+ const callOrder = [];
+ const nf = new Intl.NumberFormat([], {
+ get notation() {
+ callOrder.push("notation");
+ return notation;
+ },
+ get compactDisplay() {
+ callOrder.push("compactDisplay");
+ return value;
+ }
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("compactDisplay" in resolvedOptions, false);
+ assert.sameValue(resolvedOptions.compactDisplay, undefined);
+
+ assert.compareArray(callOrder, [
+ "notation",
+ "compactDisplay",
+ ]);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-default-value.js b/js/src/tests/test262/intl402/NumberFormat/constructor-default-value.js
new file mode 100644
index 0000000000..f92618543d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-default-value.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the constructor for Intl.NumberFormat uses appropriate default
+ values for its arguments (locales and options).
+---*/
+
+const actual = new Intl.NumberFormat().resolvedOptions();
+const expected = new Intl.NumberFormat([], Object.create(null)).resolvedOptions();
+
+assert.sameValue(actual.locale, expected.locale);
+assert.sameValue(actual.minimumIntegerDigits, expected.minimumIntegerDigits);
+assert.sameValue(actual.minimumFractionDigits, expected.minimumFractionDigits);
+assert.sameValue(actual.maximumFractionDigits, expected.maximumFractionDigits);
+assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+assert.sameValue(actual.style, expected.style);
+assert.sameValue(actual.useGrouping, expected.useGrouping);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-locales-arraylike.js b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-arraylike.js
new file mode 100644
index 0000000000..816bcd2096
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-arraylike.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the Intl.NumberFormat constructor accepts Array-like values for the
+ locales argument and treats them well.
+---*/
+
+const actual = Intl.NumberFormat({
+ length: 1,
+ 0: 'en-US'
+}).resolvedOptions();
+const expected = Intl.NumberFormat(['en-US']).resolvedOptions();
+
+assert.sameValue(actual.locale, expected.locale);
+assert.sameValue(actual.minimumIntegerDigits, expected.minimumIntegerDigits);
+assert.sameValue(actual.minimumFractionDigits, expected.minimumFractionDigits);
+assert.sameValue(actual.maximumFractionDigits, expected.maximumFractionDigits);
+assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+assert.sameValue(actual.style, expected.style);
+assert.sameValue(actual.useGrouping, expected.useGrouping);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-locales-get-tostring.js b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-get-tostring.js
new file mode 100644
index 0000000000..d7bb6422c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-get-tostring.js
@@ -0,0 +1,43 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that Get(O, P) and ToString(arg) are properly called within the
+ constructor for Intl.NumberFormat
+info: |
+ 9.2.1 CanonicalizeLocaleList ( locales )
+
+ 5. Let len be ? ToLength(? Get(O, "length")).
+
+ 7.a. Let Pk be ToString(k).
+
+ 7.c.i. Let kValue be ? Get(O, Pk).
+---*/
+
+const locales = {
+ length: 8,
+ 1: 'en-US',
+ 3: 'de-DE',
+ 5: 'en-IN',
+ 7: 'en-GB'
+};
+
+const actualLookups = [];
+const expectedLookups = Object.keys(locales);
+
+const handlers = {
+ get(obj, prop) {
+ actualLookups.push(prop);
+ return Reflect.get(...arguments);
+ }
+};
+
+const proxyLocales = new Proxy(locales, handlers);
+
+const nf = new Intl.NumberFormat(proxyLocales);
+
+expectedLookups.forEach(lookup => assert(actualLookups.indexOf(lookup) != -1));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-locales-hasproperty.js b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-hasproperty.js
new file mode 100644
index 0000000000..93784924ae
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-hasproperty.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that HasProperty(O, Pk) is properly called within the constructor for
+ Intl.NumberFormat
+info: |
+ 9.2.1 CanonicalizeLocaleList ( locales )
+
+ 7.b. Let kPresent be ? HasProperty(O, Pk).
+---*/
+
+const locales = {
+ length: 8,
+ 1: 'en-US',
+ 3: 'de-DE',
+ 5: 'en-IN',
+ 7: 'en-GB'
+};
+
+const actualLookups = [];
+
+const handlers = {
+ has(obj, prop) {
+ actualLookups.push(prop);
+ return Reflect.has(...arguments);
+ }
+};
+
+const proxyLocales = new Proxy(locales, handlers);
+
+const nf = new Intl.NumberFormat(proxyLocales);
+
+assert.sameValue(actualLookups.length, locales.length);
+for (let index in actualLookups) {
+ assert.sameValue(actualLookups[index], String(index));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-locales-string.js b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-string.js
new file mode 100644
index 0000000000..b2703b1def
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-string.js
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that passing a string value to the Intl.NumberFormat constructor is
+ equivalent to passing an Array containing the same string value.
+info: |
+ 9.2.1 CanonicalizeLocaleList ( locales )
+
+ 3 .If Type(locales) is String, then
+ a. Let O be CreateArrayFromList(« locales »).
+---*/
+
+const actual = Intl.NumberFormat('en-US').resolvedOptions();
+const expected = Intl.NumberFormat(['en-US']).resolvedOptions();
+
+assert.sameValue(actual.locale, expected.locale);
+assert.sameValue(actual.minimumIntegerDigits, expected.minimumIntegerDigits);
+assert.sameValue(actual.minimumFractionDigits, expected.minimumFractionDigits);
+assert.sameValue(actual.maximumFractionDigits, expected.maximumFractionDigits);
+assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+assert.sameValue(actual.style, expected.style);
+assert.sameValue(actual.useGrouping, expected.useGrouping);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-locales-toobject.js b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-toobject.js
new file mode 100644
index 0000000000..08df0d0580
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-locales-toobject.js
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that Intl.NumberFormat contructor converts the locales argument
+ to an object using `ToObject` (7.1.13).
+info: |
+ 9.2.1 CanonicalizeLocaleList
+
+ 4.a. Let O be ? ToObject(locales).
+---*/
+
+const toObjectResults = [
+ [true, new Boolean(true)],
+ [42, new Number(42)],
+ [{}, {}],
+ [Symbol(), Object(Symbol())]
+];
+
+// Test if ToObject is used to convert primitives to Objects.
+toObjectResults.forEach(pair => {
+ const [value, result] = pair;
+ const actual = new Intl.NumberFormat(value).resolvedOptions();
+ const expected = new Intl.NumberFormat(result).resolvedOptions()
+
+ assert.sameValue(actual.locale, expected.locale);
+ assert.sameValue(actual.minimumIntegerDigits, expected.minimumIntegerDigits);
+ assert.sameValue(actual.minimumFractionDigits, expected.minimumFractionDigits);
+ assert.sameValue(actual.maximumFractionDigits, expected.maximumFractionDigits);
+ assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+ assert.sameValue(actual.style, expected.style);
+ assert.sameValue(actual.useGrouping, expected.useGrouping);
+});
+
+// ToObject throws a TypeError for undefined and null, but it's not called
+// when locales is undefined.
+assert.throws(TypeError, () => new Intl.NumberFormat(null));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-no-instanceof.js b/js/src/tests/test262/intl402/NumberFormat/constructor-no-instanceof.js
new file mode 100644
index 0000000000..fe9f1ac720
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-no-instanceof.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl-numberformat-constructor
+description: >
+ Tests that the Intl.NumberFormat constructor calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ ChainNumberFormat ( numberFormat, newTarget, this )
+ 1. If newTarget is undefined and ? OrdinaryHasInstance(%NumberFormat%, this) is true, then
+ a. Perform ? DefinePropertyOrThrow(this, %Intl%.[[FallbackSymbol]], PropertyDescriptor{
+ [[Value]]: numberFormat, [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: false }).
+ b. Return this.
+---*/
+
+Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+Intl.NumberFormat();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-notation.js b/js/src/tests/test262/intl402/NumberFormat/constructor-notation.js
new file mode 100644
index 0000000000..91dc05937c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-notation.js
@@ -0,0 +1,33 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the notation option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 16. Let notation be ? GetOption(options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
+ 17. Set numberFormat.[[Notation]] to notation.
+
+features: [Intl.NumberFormat-unified]
+---*/
+
+const values = [
+ [undefined, "standard"],
+ ["standard"],
+ ["scientific"],
+ ["engineering"],
+ ["compact"],
+];
+
+for (const [value, expected = value] of values) {
+ const nf = new Intl.NumberFormat([], {
+ notation: value,
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("notation" in resolvedOptions, true);
+ assert.sameValue(resolvedOptions.notation, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-numberingSystem-order.js b/js/src/tests/test262/intl402/NumberFormat/constructor-numberingSystem-order.js
new file mode 100644
index 0000000000..67ec1db98a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-numberingSystem-order.js
@@ -0,0 +1,46 @@
+// Copyright 2019 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Checks the order of getting "numberingSystem" option in the
+ NumberFormat is between "localeMatcher" and "style" options.
+info: |
+ InitializeNumberFormat ( _numberFormat_, _locales_, _options_ )
+
+ 5. Let _matcher_ be ? GetOption(_options_, `"localeMatcher"`, `"string"`, &laquo; `"lookup"`, `"best fit"` &raquo;, `"best fit"`).
+ ...
+ 7. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`, `"string"`, *undefined*, *undefined*).
+ ...
+ 17. Let _style_ be ? GetOption(_options_, `"style"`, `"string"`, &laquo; `"decimal"`, `"percent"`, `"currency"` &raquo;, `"decimal"`).
+includes: [compareArray.js]
+---*/
+
+var actual = [];
+
+const options = {
+ get localeMatcher() {
+ actual.push("localeMatcher");
+ return undefined;
+ },
+ get numberingSystem() {
+ actual.push("numberingSystem");
+ return undefined;
+ },
+ get style() {
+ actual.push("style");
+ return undefined;
+ },
+};
+
+const expected = [
+ "localeMatcher",
+ "numberingSystem",
+ "style"
+];
+
+let nf = new Intl.NumberFormat(undefined, options);
+assert.compareArray(actual, expected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-option-read-order.js b/js/src/tests/test262/intl402/NumberFormat/constructor-option-read-order.js
new file mode 100644
index 0000000000..b7e29dd8cd
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-option-read-order.js
@@ -0,0 +1,57 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks the order of option read.
+features: [Intl.NumberFormat-v3]
+includes: [compareArray.js]
+---*/
+
+let optionKeys = [
+ // Inside InitializeNumberFormat
+ "localeMatcher",
+ "numberingSystem",
+ // Inside SetNumberFormatUnitOptions
+ "style",
+ "currency",
+ "currencyDisplay",
+ "currencySign",
+ "unit",
+ "unitDisplay",
+ // End of SetNumberFormatUnitOptions
+ // Back to InitializeNumberFormat
+ "notation",
+ // Inside SetNumberFormatDigitOptions
+ "minimumIntegerDigits",
+ "minimumFractionDigits",
+ "maximumFractionDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+ "roundingIncrement",
+ "roundingMode",
+ "roundingPriority",
+ "trailingZeroDisplay",
+ // End of SetNumberFormatDigitOptions
+ // Back to InitializeNumberFormat
+ "compactDisplay",
+ "useGrouping",
+ "signDisplay"
+];
+
+// Use getters to track the order of reading known properties.
+// TODO: Should we use a Proxy to detect *unexpected* property reads?
+let reads = new Array();
+let options = {};
+optionKeys.forEach((key) => {
+ Object.defineProperty(options, key, {
+ get() {
+ reads.push(key);
+ return undefined;
+ },
+ });
+});
+new Intl.NumberFormat(undefined, options);
+assert.compareArray(reads, optionKeys, "Intl.NumberFormat options read order");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-numberingSystem-invalid.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-numberingSystem-invalid.js
new file mode 100644
index 0000000000..40de2cd18d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-numberingSystem-invalid.js
@@ -0,0 +1,42 @@
+// Copyright 2020 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Checks error cases for the options argument to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ ...
+ 8. If numberingSystem is not undefined, then
+ a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+---*/
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ numberingSystem = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidNumberingSystemOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-latn-",
+ "latn-",
+ "latn--",
+ "latn-ca",
+ "latn-ca-",
+ "latn-ca-gregory",
+ "latné",
+ "latn编号",
+];
+for (const numberingSystem of invalidNumberingSystemOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.NumberFormat('en', {numberingSystem});
+ }, `new Intl.NumberFormat("en", {numberingSystem: "${numberingSystem}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-roundingMode-invalid.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-roundingMode-invalid.js
new file mode 100644
index 0000000000..b4177ef246
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-roundingMode-invalid.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Abrupt completion from invalid values for `roundingMode`
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat('en', {roundingMode: null});
+}, 'null');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat('en', {roundingMode: 3});
+}, 'number');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat('en', {roundingMode: true});
+}, 'boolean');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat('en', {roundingMode: 'HalfExpand'});
+}, 'invalid string');
+
+var symbol = Symbol('halfExpand');
+assert.throws(TypeError, function() {
+ new Intl.NumberFormat('en', {roundingMode: symbol});
+}, 'Symbol');
+
+var brokenToString = { toString: function() { throw new Test262Error(); } };
+assert.throws(Test262Error, function() {
+ new Intl.NumberFormat('en', {roundingMode: brokenToString});
+}, 'broken `toString` implementation');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-increment.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-increment.js
new file mode 100644
index 0000000000..bfe7c9648e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-increment.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: >
+ Exception from accessing the "roundingIncrement" option for the NumberFormat
+ constructor should be propagated to the caller
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(Test262Error, function() {
+ new Intl.NumberFormat('en', {
+ get roundingIncrement() {
+ throw new Test262Error();
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-mode.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-mode.js
new file mode 100644
index 0000000000..ff13e2c860
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-mode.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: >
+ Exception from accessing the "roundingMode" option for the NumberFormat
+ constructor should be propagated to the caller
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(Test262Error, function() {
+ new Intl.NumberFormat('en', {
+ get roundingMode() {
+ throw new Test262Error();
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-priority.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-priority.js
new file mode 100644
index 0000000000..fd0f55bcac
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-rounding-priority.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: >
+ Exception from accessing the "roundingPriority" option for the NumberFormat
+ constructor should be propagated to the caller
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(Test262Error, function() {
+ new Intl.NumberFormat('en', {
+ get roundingPriority() {
+ throw new Test262Error();
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-trailing-zero-display.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-trailing-zero-display.js
new file mode 100644
index 0000000000..b66f332a2c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters-trailing-zero-display.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: >
+ Exception from accessing the "trailingZeroDisplay" option for the
+ NumberFormat constructor should be propagated to the caller
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(Test262Error, function() {
+ new Intl.NumberFormat('en', {
+ get trailingZeroDisplay() {
+ throw new Test262Error();
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters.js
new file mode 100644
index 0000000000..70dafd7d34
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-throwing-getters.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks the propagation of exceptions from the options for the NumberFormat constructor.
+---*/
+
+function CustomError() {}
+
+const options = [
+ "localeMatcher",
+ "numberingSystem",
+ "style",
+ "currency",
+ "currencyDisplay",
+ "minimumIntegerDigits",
+ "minimumFractionDigits",
+ "maximumFractionDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+ "useGrouping",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.NumberFormat("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-options-toobject.js b/js/src/tests/test262/intl402/NumberFormat/constructor-options-toobject.js
new file mode 100644
index 0000000000..7544ad33fc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-options-toobject.js
@@ -0,0 +1,42 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that Intl.NumberFormat contructor converts the options argument
+ to an object using `ToObject` (7.1.13).
+info: |
+ 11.1.2 InitializeNumberFormat
+
+ 3.a. Let options be ? ToObject(options).
+---*/
+
+const toObjectResults = [
+ [true, new Boolean(true)],
+ [42, new Number(42)],
+ ['foo', new String('foo')],
+ [{}, {}],
+ [Symbol(), Object(Symbol())]
+];
+
+// Test if ToObject is used to convert primitives to Objects.
+toObjectResults.forEach(pair => {
+ const [value, result] = pair;
+ const actual = new Intl.NumberFormat(['en-US'], value).resolvedOptions();
+ const expected = new Intl.NumberFormat(['en-US'], result).resolvedOptions();
+ assert.sameValue(actual.locale, expected.locale);
+ assert.sameValue(actual.minimumIntegerDigits, expected.minimumIntegerDigits);
+ assert.sameValue(actual.minimumFractionDigits, expected.minimumFractionDigits);
+ assert.sameValue(actual.maximumFractionDigits, expected.maximumFractionDigits);
+ assert.sameValue(actual.numberingSystem, expected.numberingSystem);
+ assert.sameValue(actual.style, expected.style);
+ assert.sameValue(actual.useGrouping, expected.useGrouping);
+
+});
+
+// ToObject throws a TypeError for undefined and null, but it's not called
+// when options is undefined.
+assert.throws(TypeError, () => new Intl.NumberFormat(['en-US'], null));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-order.js b/js/src/tests/test262/intl402/NumberFormat/constructor-order.js
new file mode 100644
index 0000000000..92059305f7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-order.js
@@ -0,0 +1,30 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the unit option with the currency style.
+info: |
+ SetNumberFormatUnitOptions ( intlObj, options )
+
+ 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined).
+ 6. If currency is not undefined, then
+ a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
+ 7. If style is "currency" and currency is undefined, throw a TypeError exception.
+ ...
+ 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined).
+ 11. If unit is not undefined, then
+ a. If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
+ 12. If style is "unit" and unit is undefined, throw a TypeError exception.
+features: [Intl.NumberFormat-unified]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.NumberFormat([], { style: "currency", unit: "test" })
+});
+
+assert.throws(RangeError, () => {
+ new Intl.NumberFormat([], { style: "unit", currency: "test" })
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement-invalid.js b/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement-invalid.js
new file mode 100644
index 0000000000..5822a7f702
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement-invalid.js
@@ -0,0 +1,46 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Rejects invalid values for roundingIncrement option.
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 0});
+}, '0');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 3});
+}, '3');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 4});
+}, '4');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 5000.1});
+}, '5000.1');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 5001});
+}, '5001');
+
+assert.throws(TypeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 2, roundingPriority: 'morePrecision'});
+}, '2, roundingType is "morePrecision"');
+
+assert.throws(TypeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 2, roundingPriority: 'lessPrecision'});
+}, '2, roundingType is "lessPrecision"');
+
+assert.throws(TypeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 2, minimumSignificantDigits: 1});
+}, '2, roundingType is "significantDigits"');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {roundingIncrement: 2, maximumFractionDigits:3 , minimumFractionDigits:2 });
+}, '"maximumFractionDigits" is not equal to "minimumFractionDigits"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement.js b/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement.js
new file mode 100644
index 0000000000..3d8522d28a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-roundingIncrement.js
@@ -0,0 +1,52 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the roundingIncrement option to the NumberFormat constructor.
+includes: [compareArray.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const values = [
+ [undefined, 1],
+ [1, 1],
+ [2, 2],
+ [5, 5],
+ [10, 10],
+ [20, 20],
+ [25, 25],
+ [50, 50],
+ [100, 100],
+ [200, 200],
+ [250, 250],
+ [500, 500],
+ [1000, 1000],
+ [2000, 2000],
+ [2500, 2500],
+ [5000, 5000],
+ [true, 1],
+ ["2", 2],
+ [{valueOf: function() { return 5; }}, 5],
+];
+
+for (const [value, expected] of values) {
+ const callOrder = [];
+ const nf = new Intl.NumberFormat([], {
+ get notation() {
+ callOrder.push("notation");
+ return "standard";
+ },
+ get roundingIncrement() {
+ callOrder.push("roundingIncrement");
+ return value;
+ },
+ minimumFractionDigits: 3
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert("roundingIncrement" in resolvedOptions, "has property for value " + value);
+ assert.sameValue(resolvedOptions.roundingIncrement, expected);
+
+ assert.compareArray(callOrder, ["notation", "roundingIncrement"]);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay-negative.js b/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay-negative.js
new file mode 100644
index 0000000000..32ad4023c7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay-negative.js
@@ -0,0 +1,27 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 32. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero", "negative" », "auto").
+ 33. Set numberFormat.[[SignDisplay]] to signDisplay.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat([], {
+ signDisplay: 'negative',
+});
+const resolvedOptions = nf.resolvedOptions();
+
+verifyProperty(resolvedOptions, 'signDisplay', {
+ value: 'negative',
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay.js b/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay.js
new file mode 100644
index 0000000000..2b458367c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-signDisplay.js
@@ -0,0 +1,33 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 23. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
+ 24. Set numberFormat.[[SignDisplay]] to signDisplay.
+
+features: [Intl.NumberFormat-unified]
+---*/
+
+const values = [
+ [undefined, "auto"],
+ ["auto"],
+ ["never"],
+ ["always"],
+ ["exceptZero"],
+];
+
+for (const [value, expected = value] of values) {
+ const nf = new Intl.NumberFormat([], {
+ signDisplay: value,
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("signDisplay" in resolvedOptions, true);
+ assert.sameValue(resolvedOptions.signDisplay, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay-invalid.js b/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay-invalid.js
new file mode 100644
index 0000000000..6a5f620a2a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay-invalid.js
@@ -0,0 +1,33 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Rejects invalid values for trailingZeroDisplay option.
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: ''});
+}, 'empty string');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: 'Auto'});
+}, 'Auto');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: 'StripIfInteger'});
+}, 'StripIfInteger');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: 'stripifinteger'});
+}, 'stripifinteger');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: ' auto'});
+}, '" auto" (with leading space)');
+
+assert.throws(RangeError, function() {
+ new Intl.NumberFormat([], {trailingZeroDisplay: 'auto '});
+}, '"auto " (with trailing space)');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay.js b/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay.js
new file mode 100644
index 0000000000..d474ef4aa3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-trailingZeroDisplay.js
@@ -0,0 +1,36 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the trailingZeroDisplay option to the NumberFormat constructor.
+includes: [compareArray.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const values = [
+ [undefined, "auto"],
+ ["auto", "auto"],
+ ["stripIfInteger", "stripIfInteger"],
+ [{toString: function() { return "stripIfInteger"; }}, "stripIfInteger"],
+];
+
+for (const [value, expected] of values) {
+ const callOrder = [];
+ const nf = new Intl.NumberFormat([], {
+ get roundingIncrement() {
+ callOrder.push("roundingIncrement");
+ return 1;
+ },
+ get trailingZeroDisplay() {
+ callOrder.push("trailingZeroDisplay");
+ return value;
+ }
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert("trailingZeroDisplay" in resolvedOptions, "has property for value " + value);
+ assert.sameValue(resolvedOptions.trailingZeroDisplay, expected);
+
+ assert.compareArray(callOrder, ["roundingIncrement", "trailingZeroDisplay"]);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-unit.js b/js/src/tests/test262/intl402/NumberFormat/constructor-unit.js
new file mode 100644
index 0000000000..36f2e8a002
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-unit.js
@@ -0,0 +1,65 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the unit style.
+includes: [testIntl.js]
+features: [Intl.NumberFormat-unified]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.NumberFormat([], {
+ style: "unit",
+ })
+});
+
+for (const unit of ["test", "MILE", "kB"]) {
+ // Throws RangeError for invalid unit identifier.
+ for (const style of [undefined, "decimal", "unit"]) {
+ assert.throws(RangeError, () => {
+ new Intl.NumberFormat([], { style, unit })
+ }, `{ style: ${style}, unit: ${unit} }`);
+ }
+
+ const style = "currency";
+
+ // Throws TypeError because "currency" option is missing.
+ assert.throws(TypeError, () => {
+ new Intl.NumberFormat([], { style, unit })
+ }, `{ style: ${style}, unit: ${unit} }`);
+
+ // Throws RangeError for invalid unit identifier.
+ assert.throws(RangeError, () => {
+ new Intl.NumberFormat([], { style, unit, currency: "USD" })
+ }, `{ style: ${style}, unit: ${unit} }`);
+}
+
+const nf = new Intl.NumberFormat([], {
+ style: "percent",
+});
+assert.sameValue(nf.resolvedOptions().style, "percent");
+assert.sameValue("unit" in nf.resolvedOptions(), false);
+assert.sameValue(nf.resolvedOptions().unit, undefined);
+
+function check(unit) {
+ const nf = new Intl.NumberFormat([], {
+ style: "unit",
+ unit,
+ });
+ const options = nf.resolvedOptions();
+ assert.sameValue(options.style, "unit");
+ assert.sameValue(options.unit, unit);
+}
+
+const units = allSimpleSanctionedUnits();
+
+for (const simpleUnit of units) {
+ check(simpleUnit);
+ for (const simpleUnit2 of units) {
+ check(simpleUnit + "-per-" + simpleUnit2);
+ check(simpleUnit2 + "-per-" + simpleUnit);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/constructor-unitDisplay.js b/js/src/tests/test262/intl402/NumberFormat/constructor-unitDisplay.js
new file mode 100644
index 0000000000..54b1594aa5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/constructor-unitDisplay.js
@@ -0,0 +1,68 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 23. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
+ 24. Set numberFormat.[[SignDisplay]] to signDisplay.
+
+features: [Intl.NumberFormat-unified]
+---*/
+
+const values = [
+ [undefined, "short"],
+ ["short"],
+ ["narrow"],
+ ["long"],
+];
+
+for (const [value, expected = value] of values) {
+ const nf = new Intl.NumberFormat([], {
+ style: "unit",
+ unitDisplay: value,
+ unit: "hour",
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("unitDisplay" in resolvedOptions, true);
+ assert.sameValue(resolvedOptions.unitDisplay, expected);
+}
+
+for (const [value, expected = value] of values) {
+ const nf = new Intl.NumberFormat([], {
+ style: "unit",
+ unitDisplay: value,
+ unit: "percent",
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("unitDisplay" in resolvedOptions, true);
+ assert.sameValue(resolvedOptions.unitDisplay, expected);
+}
+
+for (const [value] of values) {
+ const nf = new Intl.NumberFormat([], {
+ style: "percent",
+ unitDisplay: value,
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue("unitDisplay" in resolvedOptions, false);
+ assert.sameValue(resolvedOptions.unitDisplay, undefined);
+}
+
+const invalidValues = [
+ "",
+ "Short",
+ "s",
+ "\u017Fhort",
+];
+
+for (const unitDisplay of invalidValues) {
+ assert.throws(RangeError, () => {
+ new Intl.NumberFormat([], { unitDisplay });
+ });
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/currency-code-invalid.js b/js/src/tests/test262/intl402/NumberFormat/currency-code-invalid.js
new file mode 100644
index 0000000000..22c43ffb18
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/currency-code-invalid.js
@@ -0,0 +1,29 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.3.1_b
+description: Tests that invalid currency codes are not accepted.
+author: Norbert Lindenberg
+---*/
+
+var invalidCurrencyCodes = [
+ "",
+ "€",
+ "$",
+ "SFr.",
+ "DM",
+ "KR₩",
+ "702",
+ "ßP",
+ "ınr"
+];
+
+invalidCurrencyCodes.forEach(function (code) {
+ // this must throw an exception for an invalid currency code
+ assert.throws(RangeError, function() {
+ var format = new Intl.NumberFormat(["de-de"], {style: "currency", currency: code});
+ }, "Invalid currency code '" + code + "' was not rejected.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/currency-code-well-formed.js b/js/src/tests/test262/intl402/NumberFormat/currency-code-well-formed.js
new file mode 100644
index 0000000000..0b76772b1e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/currency-code-well-formed.js
@@ -0,0 +1,24 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.3.1_a
+description: Tests that well-formed currency codes are accepted.
+author: Norbert Lindenberg
+---*/
+
+var wellFormedCurrencyCodes = [
+ "BOB",
+ "EUR",
+ "usd", // currency codes are case-insensitive
+ "XdR",
+ "xTs"
+];
+
+wellFormedCurrencyCodes.forEach(function (code) {
+ // this must not throw an exception for a valid currency code
+ var format = new Intl.NumberFormat(["de-de"], {style: "currency", currency: code});
+ assert.sameValue(format.resolvedOptions().currency, code.toUpperCase(), "Currency " + code + " was not correctly accepted.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/currency-digits.js b/js/src/tests/test262/intl402/NumberFormat/currency-digits.js
new file mode 100644
index 0000000000..adba633083
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/currency-digits.js
@@ -0,0 +1,191 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_20_c
+description: >
+ Tests that the number of fractional digits is determined correctly
+ for currencies.
+author: Norbert Lindenberg
+---*/
+
+// data from https://www.currency-iso.org/dam/downloads/lists/list_one.xml, 2017-09-22
+var currencyDigits = {
+ AED: 2,
+ AFN: 2,
+ ALL: 2,
+ AMD: 2,
+ ANG: 2,
+ AOA: 2,
+ ARS: 2,
+ AUD: 2,
+ AWG: 2,
+ AZN: 2,
+ BAM: 2,
+ BBD: 2,
+ BDT: 2,
+ BGN: 2,
+ BHD: 3,
+ BIF: 0,
+ BMD: 2,
+ BND: 2,
+ BOB: 2,
+ BOV: 2,
+ BRL: 2,
+ BSD: 2,
+ BTN: 2,
+ BWP: 2,
+ BYN: 2,
+ BZD: 2,
+ CAD: 2,
+ CDF: 2,
+ CHE: 2,
+ CHF: 2,
+ CHW: 2,
+ CLF: 4,
+ CLP: 0,
+ CNY: 2,
+ COP: 2,
+ COU: 2,
+ CRC: 2,
+ CUC: 2,
+ CUP: 2,
+ CVE: 2,
+ CZK: 2,
+ DJF: 0,
+ DKK: 2,
+ DOP: 2,
+ DZD: 2,
+ EGP: 2,
+ ERN: 2,
+ ETB: 2,
+ EUR: 2,
+ FJD: 2,
+ FKP: 2,
+ GBP: 2,
+ GEL: 2,
+ GHS: 2,
+ GIP: 2,
+ GMD: 2,
+ GNF: 0,
+ GTQ: 2,
+ GYD: 2,
+ HKD: 2,
+ HNL: 2,
+ HRK: 2,
+ HTG: 2,
+ HUF: 2,
+ IDR: 2,
+ ILS: 2,
+ INR: 2,
+ IQD: 3,
+ IRR: 2,
+ ISK: 0,
+ JMD: 2,
+ JOD: 3,
+ JPY: 0,
+ KES: 2,
+ KGS: 2,
+ KHR: 2,
+ KMF: 0,
+ KPW: 2,
+ KRW: 0,
+ KWD: 3,
+ KYD: 2,
+ KZT: 2,
+ LAK: 2,
+ LBP: 2,
+ LKR: 2,
+ LRD: 2,
+ LSL: 2,
+ LYD: 3,
+ MAD: 2,
+ MDL: 2,
+ MGA: 2,
+ MKD: 2,
+ MMK: 2,
+ MNT: 2,
+ MOP: 2,
+ MRO: 2,
+ MUR: 2,
+ MVR: 2,
+ MWK: 2,
+ MXN: 2,
+ MXV: 2,
+ MYR: 2,
+ MZN: 2,
+ NAD: 2,
+ NGN: 2,
+ NIO: 2,
+ NOK: 2,
+ NPR: 2,
+ NZD: 2,
+ OMR: 3,
+ PAB: 2,
+ PEN: 2,
+ PGK: 2,
+ PHP: 2,
+ PKR: 2,
+ PLN: 2,
+ PYG: 0,
+ QAR: 2,
+ RON: 2,
+ RSD: 2,
+ RUB: 2,
+ RWF: 0,
+ SAR: 2,
+ SBD: 2,
+ SCR: 2,
+ SDG: 2,
+ SEK: 2,
+ SGD: 2,
+ SHP: 2,
+ SLL: 2,
+ SOS: 2,
+ SRD: 2,
+ SSP: 2,
+ STD: 2,
+ SVC: 2,
+ SYP: 2,
+ SZL: 2,
+ THB: 2,
+ TJS: 2,
+ TMT: 2,
+ TND: 3,
+ TOP: 2,
+ TRY: 2,
+ TTD: 2,
+ TWD: 2,
+ TZS: 2,
+ UAH: 2,
+ UGX: 0,
+ USD: 2,
+ USN: 2,
+ UYI: 0,
+ UYU: 2,
+ UZS: 2,
+ VEF: 2,
+ VND: 0,
+ VUV: 0,
+ WST: 2,
+ XAF: 0,
+ XCD: 2,
+ XOF: 0,
+ XPF: 0,
+ YER: 2,
+ ZAR: 2,
+ ZMW: 2,
+ ZWL: 2,
+};
+
+Object.getOwnPropertyNames(currencyDigits).forEach(function (currency) {
+ var digits = currencyDigits[currency];
+ var format = Intl.NumberFormat([], {style: "currency", currency: currency});
+ var min = format.resolvedOptions().minimumFractionDigits;
+ var max = format.resolvedOptions().maximumFractionDigits;
+ assert.sameValue(min, digits, "Didn't get correct minimumFractionDigits for currency " + currency + ".");
+ assert.sameValue(max, digits, "Didn't get correct maximumFractionDigits for currency " + currency + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/currencyDisplay-unit.js b/js/src/tests/test262/intl402/NumberFormat/currencyDisplay-unit.js
new file mode 100644
index 0000000000..bc760e490c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/currencyDisplay-unit.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-setnumberformatunitoptions
+description: Checks handling of valid values for the numeric option to the NumberFormat constructor.
+info: |
+ SetNumberFormatUnitOptions ( intlObj, options )
+
+ 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string", « "code", "symbol", "narrowSymbol", "name" », "symbol").
+ 11. If style is "currency", then
+ f. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
+
+features: [Intl.NumberFormat-unified]
+---*/
+
+const validOptions = [
+ [undefined, "symbol"],
+ ["narrowSymbol", "narrowSymbol"],
+ [{ toString() { return "narrowSymbol"; } }, "narrowSymbol"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const nf = new Intl.NumberFormat([], {
+ "style": "currency",
+ "currency": "EUR",
+ "currencyDisplay": validOption,
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue(resolvedOptions.currencyDisplay, expected);
+}
+
+for (const [validOption] of validOptions) {
+ const nf = new Intl.NumberFormat([], {
+ "style": "percent",
+ "currencyDisplay": validOption,
+ });
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue(resolvedOptions.currencyDisplay, undefined);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/default-minimum-singificant-digits.js b/js/src/tests/test262/intl402/NumberFormat/default-minimum-singificant-digits.js
new file mode 100644
index 0000000000..a9cab8c641
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/default-minimum-singificant-digits.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Tests that the default value of minimumSignificantDigits is 1.
+esid: sec-setnfdigitoptions
+---*/
+
+// maximumSignificantDigits needs to be in range from minimumSignificantDigits
+// to 21 (both inclusive). Setting maximumSignificantDigits to 0 will throw a
+// RangeError if the (default) minimumSignificantDigits is at least 1.
+assert.throws(RangeError, function() {
+ Intl.NumberFormat(undefined, {maximumSignificantDigits: 0});
+});
+
+// If nothing is thrown, check that the options are resolved appropriately.
+var res = Intl.NumberFormat(undefined, {maximumSignificantDigits: 1})
+
+assert.sameValue(Object.getPrototypeOf(res), Intl.NumberFormat.prototype, 'result is an instance of NumberFormat')
+assert.sameValue(res.resolvedOptions().minimumSignificantDigits, 1, 'default minimumSignificantDigits')
+assert.sameValue(res.resolvedOptions().maximumSignificantDigits, 1, 'sets maximumSignificantDigits')
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/default-options-object-prototype.js b/js/src/tests/test262/intl402/NumberFormat/default-options-object-prototype.js
new file mode 100644
index 0000000000..8f43d572f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/default-options-object-prototype.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for NumberFormat as a null prototype is used.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+---*/
+
+let defaultMaximumFractionDigits =
+ new Intl.NumberFormat("en").resolvedOptions().maximumFractionDigits;
+
+Object.prototype.maximumFractionDigits = 1;
+let formatter = new Intl.NumberFormat("en");
+assert.sameValue(formatter.resolvedOptions().maximumFractionDigits,
+ defaultMaximumFractionDigits);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/dft-currency-mnfd-range-check-mxfd.js b/js/src/tests/test262/intl402/NumberFormat/dft-currency-mnfd-range-check-mxfd.js
new file mode 100644
index 0000000000..3db6686349
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/dft-currency-mnfd-range-check-mxfd.js
@@ -0,0 +1,24 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-setnfdigitoptions
+description: >
+ When a currency is used in Intl.NumberFormat and minimumFractionDigits is
+ not provided, maximumFractionDigits should be set as provided.
+---*/
+
+assert.sameValue((new Intl.NumberFormat('en', {
+ style: 'currency',
+ currency: 'USD',
+ maximumFractionDigits: 1
+})).resolvedOptions().maximumFractionDigits, 1);
+
+assert.sameValue((new Intl.NumberFormat('en', {
+ style: 'currency',
+ currency: 'CLF',
+ maximumFractionDigits: 3
+})).resolvedOptions().maximumFractionDigits, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/fraction-digit-options-read-once.js b/js/src/tests/test262/intl402/NumberFormat/fraction-digit-options-read-once.js
new file mode 100644
index 0000000000..faf89e8f9d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/fraction-digit-options-read-once.js
@@ -0,0 +1,20 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-setnfdigitoptions
+description: >
+ The maximum and minimum fraction digits properties should be read from
+ the options bag exactly once from the NumberFormat constructor.
+info: Regression test for https://bugs.chromium.org/p/v8/issues/detail?id=6015
+---*/
+
+var calls = [];
+
+new Intl.NumberFormat("en", { get minimumFractionDigits() { calls.push('minimumFractionDigits') },
+ get maximumFractionDigits() { calls.push('maximumFractionDigits') } });
+assert.sameValue(calls.length, 2);
+assert.sameValue(calls[0], 'minimumFractionDigits');
+assert.sameValue(calls[1], 'maximumFractionDigits');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/ignore-invalid-unicode-ext-values.js b/js/src/tests/test262/intl402/NumberFormat/ignore-invalid-unicode-ext-values.js
new file mode 100644
index 0000000000..01ac841152
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/ignore-invalid-unicode-ext-values.js
@@ -0,0 +1,38 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.2.3_b
+description: >
+ Tests that Intl.NumberFormat does not accept Unicode locale
+ extension keys and values that are not allowed.
+author: Norbert Lindenberg
+---*/
+
+var locales = ["ja-JP", "zh-Hans-CN", "zh-Hant-TW"];
+var input = 1234567.89;
+
+locales.forEach(function (locale) {
+ var defaultNumberFormat = new Intl.NumberFormat([locale]);
+ var defaultOptions = defaultNumberFormat.resolvedOptions();
+ var defaultOptionsJSON = JSON.stringify(defaultOptions);
+ var defaultLocale = defaultOptions.locale;
+ var defaultFormatted = defaultNumberFormat.format(input);
+
+ var keyValues = {
+ "cu": ["USD", "EUR", "JPY", "CNY", "TWD", "invalid"],
+ "nu": ["native", "traditio", "finance", "invalid"]
+ };
+
+ Object.getOwnPropertyNames(keyValues).forEach(function (key) {
+ keyValues[key].forEach(function (value) {
+ var numberFormat = new Intl.NumberFormat([locale + "-u-" + key + "-" + value]);
+ var options = numberFormat.resolvedOptions();
+ assert.sameValue(options.locale, defaultLocale, "Locale " + options.locale + " is affected by key " + key + "; value " + value + ".");
+ assert.sameValue(JSON.stringify(options), defaultOptionsJSON, "Resolved options " + JSON.stringify(options) + " are affected by key " + key + "; value " + value + ".");
+ assert.sameValue(numberFormat.format(input), defaultFormatted, "Formatted value " + numberFormat.format(input) + " is affected by key " + key + "; value " + value + ".");
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/instance-proto-and-extensible.js b/js/src/tests/test262/intl402/NumberFormat/instance-proto-and-extensible.js
new file mode 100644
index 0000000000..0a2b3e8999
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/instance-proto-and-extensible.js
@@ -0,0 +1,19 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.3
+description: >
+ Tests that objects constructed by Intl.NumberFormat have the
+ specified internal properties.
+author: Norbert Lindenberg
+---*/
+
+var obj = new Intl.NumberFormat();
+
+var actualPrototype = Object.getPrototypeOf(obj);
+assert.sameValue(actualPrototype, Intl.NumberFormat.prototype, "Prototype of object constructed by Intl.NumberFormat isn't Intl.NumberFormat.prototype.");
+
+assert(Object.isExtensible(obj), "Object constructed by Intl.NumberFormat must be extensible.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol-on-unwrap.js b/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol-on-unwrap.js
new file mode 100644
index 0000000000..aac9c4fc1e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol-on-unwrap.js
@@ -0,0 +1,34 @@
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-unwrapnumberformat
+description: >
+ Tests that [[FallbackSymbol]]'s [[Description]] is "IntlLegacyConstructedSymbol" if normative optional is implemented.
+author: Yusuke Suzuki
+features: [intl-normative-optional]
+---*/
+
+let object = new Intl.NumberFormat();
+let newObject = Intl.NumberFormat.call(object);
+let symbol = null;
+let error = null;
+try {
+ let proxy = new Proxy(newObject, {
+ get(target, property) {
+ symbol = property;
+ return target[property];
+ }
+ });
+ Intl.NumberFormat.prototype.resolvedOptions.call(proxy);
+} catch (e) {
+ // If normative optional is not implemented, an error will be thrown.
+ error = e;
+ assert(error instanceof TypeError);
+}
+if (error === null) {
+ assert.sameValue(typeof symbol, "symbol");
+ assert.sameValue(symbol.description, "IntlLegacyConstructedSymbol");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol.js b/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol.js
new file mode 100644
index 0000000000..bf038e96ad
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/intl-legacy-constructed-symbol.js
@@ -0,0 +1,20 @@
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat
+description: >
+ Tests that [[FallbackSymbol]]'s [[Description]] is "IntlLegacyConstructedSymbol" if normative optional is implemented.
+author: Yusuke Suzuki
+features: [intl-normative-optional]
+---*/
+
+let object = new Intl.NumberFormat();
+let newObject = Intl.NumberFormat.call(object);
+let symbols = Object.getOwnPropertySymbols(newObject);
+if (symbols.length !== 0) {
+ assert.sameValue(symbols.length, 1);
+ assert.sameValue(symbols[0].description, "IntlLegacyConstructedSymbol");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/legacy-regexp-statics-not-modified.js b/js/src/tests/test262/intl402/NumberFormat/legacy-regexp-statics-not-modified.js
new file mode 100644
index 0000000000..ba010300c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/legacy-regexp-statics-not-modified.js
@@ -0,0 +1,21 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_a
+description: >
+ Tests that constructing a NumberFormat doesn't create or modify
+ unwanted properties on the RegExp constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testForUnwantedRegExpChanges(function () {
+ new Intl.NumberFormat("de-DE-u-nu-latn");
+});
+
+testForUnwantedRegExpChanges(function () {
+ new Intl.NumberFormat("de-DE-u-nu-latn", {style: "currency", currency: "EUR"});
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/length.js b/js/src/tests/test262/intl402/NumberFormat/length.js
new file mode 100644
index 0000000000..027ddb1783
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/length.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat
+description: >
+ Intl.NumberFormat.length is 0.
+info: |
+ Intl.NumberFormat ( [ locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(Intl.NumberFormat.length, 0);
+
+verifyProperty(Intl.NumberFormat, 'length', {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/name.js b/js/src/tests/test262/intl402/NumberFormat/name.js
new file mode 100644
index 0000000000..aa918694fc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat
+description: >
+ Intl.NumberFormat.name is "NumberFormat".
+info: |
+ 11.2.1 Intl.NumberFormat ([ locales [ , options ]])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat, 'name', {
+ value: 'NumberFormat',
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/numbering-system-options.js b/js/src/tests/test262/intl402/NumberFormat/numbering-system-options.js
new file mode 100644
index 0000000000..6c199f9543
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/numbering-system-options.js
@@ -0,0 +1,64 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options numberingSystem and calendar can be set through
+ either the locale or the options.
+author: Norbert Lindenberg, Daniel Ehrenberg
+---*/
+
+let defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
+
+let supportedNumberingSystems = ["latn", "arab"].filter(nu =>
+ new Intl.NumberFormat(defaultLocale + "-u-nu-" + nu)
+ .resolvedOptions().numberingSystem === nu
+);
+
+let options = [
+ {key: "nu", property: "numberingSystem", type: "string", values: supportedNumberingSystems},
+];
+
+options.forEach(function (option) {
+ let numberFormat, opt, result;
+
+ // find out which values are supported for a property in the default locale
+ let supportedValues = [];
+ option.values.forEach(function (value) {
+ opt = {};
+ opt[option.property] = value;
+ numberFormat = new Intl.NumberFormat([defaultLocale], opt);
+ result = numberFormat.resolvedOptions()[option.property];
+ if (result !== undefined && supportedValues.indexOf(result) === -1) {
+ supportedValues.push(result);
+ }
+ });
+
+ // verify that the supported values can also be set through the locale
+ supportedValues.forEach(function (value) {
+ numberFormat = new Intl.NumberFormat([defaultLocale + "-u-" + option.key + "-" + value]);
+ result = numberFormat.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Property " + option.property + " couldn't be set through locale extension key " + option.key + ".");
+ });
+
+ // verify that the options setting overrides the locale setting
+ supportedValues.forEach(function (value) {
+ let otherValue;
+ option.values.forEach(function (possibleValue) {
+ if (possibleValue !== value) {
+ otherValue = possibleValue;
+ }
+ });
+ if (otherValue !== undefined) {
+ opt = {};
+ opt[option.property] = value;
+ numberFormat = new Intl.NumberFormat([defaultLocale + "-u-" + option.key + "-" + otherValue], opt);
+ result = numberFormat.resolvedOptions()[option.property];
+ assert.sameValue(result, value, "Options value for property " + option.property + " doesn't override locale extension key " + option.key + ".");
+ }
+ });
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prop-desc.js
new file mode 100644
index 0000000000..589dbe8e32
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat-intro
+description: >
+ "NumberFormat" property of Intl.
+info: |
+ Intl.NumberFormat (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, 'NumberFormat', {
+ writable: true,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/NumberFormat/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..a8e2be5373
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/proto-from-ctor-realm.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.NumberFormat ( [ locales [ , options ] ] )
+
+ 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
+ 2. Let numberFormat be ? OrdinaryCreateFromConstructor(newTarget, "%NumberFormatPrototype%", « ... »).
+ ...
+ 6. Return numberFormat.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var nf;
+
+newTarget.prototype = undefined;
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = true;
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 0;
+nf = Reflect.construct(Intl.NumberFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(nf), other.Intl.NumberFormat.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/builtin.js
new file mode 100644
index 0000000000..95eef9d6b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/builtin.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.3_L15
+description: >
+ Tests that Intl.NumberFormat.prototype meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+---*/
+
+assert(Object.isExtensible(Intl.NumberFormat.prototype), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.NumberFormat.prototype), Object.prototype,
+ "Built-in prototype objects must have Object.prototype as their prototype.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..ba521fdc55
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.constructor
+description: >
+ "constructor" property of Intl.NumberFormat.prototype.
+info: |
+ Intl.NumberFormat.prototype.constructor
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype, "constructor", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/value.js b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/value.js
new file mode 100644
index 0000000000..28cb427436
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/constructor/value.js
@@ -0,0 +1,14 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.1
+description: >
+ Tests that Intl.NumberFormat.prototype.constructor is the
+ Intl.NumberFormat.
+author: Roozbeh Pournader
+---*/
+
+assert.sameValue(Intl.NumberFormat.prototype.constructor, Intl.NumberFormat, "Intl.NumberFormat.prototype.constructor is not the same as Intl.NumberFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/bound-to-numberformat-instance.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/bound-to-numberformat-instance.js
new file mode 100644
index 0000000000..f1817ba06f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/bound-to-numberformat-instance.js
@@ -0,0 +1,37 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_1_c
+description: Tests that format function is bound to its Intl.NumberFormat.
+author: Norbert Lindenberg
+---*/
+
+var numbers = [0, -0, 1, -1, 5.5, 123, -123, -123.45, 123.44501, 0.001234,
+ -0.00000000123, 0.00000000000000000000000000000123, 1.2, 0.0000000012344501,
+ 123445.01, 12344501000000000000000000000000000, -12344501000000000000000000000000000,
+ Infinity, -Infinity, NaN];
+var locales = [undefined, ["de"], ["th-u-nu-thai"], ["en"], ["ja-u-nu-jpanfin"], ["ar-u-nu-arab"]];
+var options = [
+ undefined,
+ {style: "percent"},
+ {style: "currency", currency: "EUR", currencyDisplay: "symbol"},
+ {style: "currency", currency: "IQD", currencyDisplay: "symbol"},
+ {style: "currency", currency: "KMF", currencyDisplay: "symbol"},
+ {style: "currency", currency: "CLF", currencyDisplay: "symbol"},
+ {useGrouping: false, minimumIntegerDigits: 3, minimumFractionDigits: 1, maximumFractionDigits: 3}
+];
+
+locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var formatObj = new Intl.NumberFormat(locales, options);
+ var formatFunc = formatObj.format;
+ numbers.forEach(function (number) {
+ var referenceFormatted = formatObj.format(number);
+ var formatted = formatFunc(number);
+ assert.sameValue(referenceFormatted, formatted, "format function produces different result than format method for locales " + locales + "; options: " + (options ? JSON.stringify(options) : options) + ".");
+ });
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/builtin.js
new file mode 100644
index 0000000000..6e264f8d26
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/builtin.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_L15
+description: >
+ Tests that the getter for Intl.NumberFormat.prototype.format
+ meets the requirements for built-in objects defined by the
+ introduction of chapter 17 of the ECMAScript Language
+ Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var formatFn = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get;
+
+assert.sameValue(Object.prototype.toString.call(formatFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatFn), Function.prototype);
+
+assert.sameValue(formatFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/default-value.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/default-value.js
new file mode 100644
index 0000000000..b4ee1e8fde
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/default-value.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-number-format-functions
+description: >
+ Tests that the default value for the argument of
+ Intl.NumberFormat.prototype.format (value) is undefined.
+info: |
+ 11.1.4 Number Format Functions
+
+ 3. If value is not provided, let value be undefined.
+ 4. Let x be ? ToNumber(value).
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// In most locales this is string "NaN", but there are exceptions, cf. "ليس رقم"
+// in Arabic, "epäluku" in Finnish, "не число" in Russian, "son emas" in Uzbek etc.
+const resultNaN = nf.format(NaN);
+
+assert.sameValue(nf.format(), resultNaN);
+assert.sameValue(nf.format(undefined), resultNaN);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-de-DE.js
new file mode 100644
index 0000000000..8c70c5e94a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-de-DE.js
@@ -0,0 +1,78 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the engineering and scientific notations.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ 0.000345,
+ "345E-6",
+ "3,45E-4",
+ ],
+ [
+ 0.345,
+ "345E-3",
+ "3,45E-1",
+ ],
+ [
+ 3.45,
+ "3,45E0",
+ "3,45E0",
+ ],
+ [
+ 34.5,
+ "34,5E0",
+ "3,45E1",
+ ],
+ [
+ 543,
+ "543E0",
+ "5,43E2",
+ ],
+ [
+ 5430,
+ "5,43E3",
+ "5,43E3",
+ ],
+ [
+ 543000,
+ "543E3",
+ "5,43E5",
+ ],
+ [
+ 543211.1,
+ "543,211E3",
+ "5,432E5",
+ ],
+ [
+ -Infinity,
+ "-∞",
+ "-∞",
+ ],
+ [
+ Infinity,
+ "∞",
+ "∞",
+ ],
+ [
+ NaN,
+ "NaN",
+ "NaN",
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("de-DE", { notation: "engineering" }));
+ assert.sameValue(nfEngineering.format(number), engineering);
+ const nfScientific = (new Intl.NumberFormat("de-DE", { notation: "scientific" }));
+ assert.sameValue(nfScientific.format(number), scientific);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-en-US.js
new file mode 100644
index 0000000000..5d8c94ce9a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-en-US.js
@@ -0,0 +1,78 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the engineering and scientific notations.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ 0.000345,
+ "345E-6",
+ "3.45E-4",
+ ],
+ [
+ 0.345,
+ "345E-3",
+ "3.45E-1",
+ ],
+ [
+ 3.45,
+ "3.45E0",
+ "3.45E0",
+ ],
+ [
+ 34.5,
+ "34.5E0",
+ "3.45E1",
+ ],
+ [
+ 543,
+ "543E0",
+ "5.43E2",
+ ],
+ [
+ 5430,
+ "5.43E3",
+ "5.43E3",
+ ],
+ [
+ 543000,
+ "543E3",
+ "5.43E5",
+ ],
+ [
+ 543211.1,
+ "543.211E3",
+ "5.432E5",
+ ],
+ [
+ -Infinity,
+ "-∞",
+ "-∞",
+ ],
+ [
+ Infinity,
+ "∞",
+ "∞",
+ ],
+ [
+ NaN,
+ "NaN",
+ "NaN",
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("en-US", { notation: "engineering" }));
+ assert.sameValue(nfEngineering.format(number), engineering);
+ const nfScientific = (new Intl.NumberFormat("en-US", { notation: "scientific" }));
+ assert.sameValue(nfScientific.format(number), scientific);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ja-JP.js
new file mode 100644
index 0000000000..85dd68d736
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ja-JP.js
@@ -0,0 +1,78 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the engineering and scientific notations.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ 0.000345,
+ "345E-6",
+ "3.45E-4",
+ ],
+ [
+ 0.345,
+ "345E-3",
+ "3.45E-1",
+ ],
+ [
+ 3.45,
+ "3.45E0",
+ "3.45E0",
+ ],
+ [
+ 34.5,
+ "34.5E0",
+ "3.45E1",
+ ],
+ [
+ 543,
+ "543E0",
+ "5.43E2",
+ ],
+ [
+ 5430,
+ "5.43E3",
+ "5.43E3",
+ ],
+ [
+ 543000,
+ "543E3",
+ "5.43E5",
+ ],
+ [
+ 543211.1,
+ "543.211E3",
+ "5.432E5",
+ ],
+ [
+ -Infinity,
+ "-∞",
+ "-∞",
+ ],
+ [
+ Infinity,
+ "∞",
+ "∞",
+ ],
+ [
+ NaN,
+ "NaN",
+ "NaN",
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("ja-JP", { notation: "engineering" }));
+ assert.sameValue(nfEngineering.format(number), engineering);
+ const nfScientific = (new Intl.NumberFormat("ja-JP", { notation: "scientific" }));
+ assert.sameValue(nfScientific.format(number), scientific);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ko-KR.js
new file mode 100644
index 0000000000..23b5632f0b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-ko-KR.js
@@ -0,0 +1,78 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the engineering and scientific notations.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ 0.000345,
+ "345E-6",
+ "3.45E-4",
+ ],
+ [
+ 0.345,
+ "345E-3",
+ "3.45E-1",
+ ],
+ [
+ 3.45,
+ "3.45E0",
+ "3.45E0",
+ ],
+ [
+ 34.5,
+ "34.5E0",
+ "3.45E1",
+ ],
+ [
+ 543,
+ "543E0",
+ "5.43E2",
+ ],
+ [
+ 5430,
+ "5.43E3",
+ "5.43E3",
+ ],
+ [
+ 543000,
+ "543E3",
+ "5.43E5",
+ ],
+ [
+ 543211.1,
+ "543.211E3",
+ "5.432E5",
+ ],
+ [
+ -Infinity,
+ "-∞",
+ "-∞",
+ ],
+ [
+ Infinity,
+ "∞",
+ "∞",
+ ],
+ [
+ NaN,
+ "NaN",
+ "NaN",
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("ko-KR", { notation: "engineering" }));
+ assert.sameValue(nfEngineering.format(number), engineering);
+ const nfScientific = (new Intl.NumberFormat("ko-KR", { notation: "scientific" }));
+ assert.sameValue(nfScientific.format(number), scientific);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-zh-TW.js
new file mode 100644
index 0000000000..5f4cacbb0b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/engineering-scientific-zh-TW.js
@@ -0,0 +1,78 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the engineering and scientific notations.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ 0.000345,
+ "345E-6",
+ "3.45E-4",
+ ],
+ [
+ 0.345,
+ "345E-3",
+ "3.45E-1",
+ ],
+ [
+ 3.45,
+ "3.45E0",
+ "3.45E0",
+ ],
+ [
+ 34.5,
+ "34.5E0",
+ "3.45E1",
+ ],
+ [
+ 543,
+ "543E0",
+ "5.43E2",
+ ],
+ [
+ 5430,
+ "5.43E3",
+ "5.43E3",
+ ],
+ [
+ 543000,
+ "543E3",
+ "5.43E5",
+ ],
+ [
+ 543211.1,
+ "543.211E3",
+ "5.432E5",
+ ],
+ [
+ -Infinity,
+ "-∞",
+ "-∞",
+ ],
+ [
+ Infinity,
+ "∞",
+ "∞",
+ ],
+ [
+ NaN,
+ "非數值",
+ "非數值",
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("zh-TW", { notation: "engineering" }));
+ assert.sameValue(nfEngineering.format(number), engineering);
+ const nfScientific = (new Intl.NumberFormat("zh-TW", { notation: "scientific" }));
+ assert.sameValue(nfScientific.format(number), scientific);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits-precision.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits-precision.js
new file mode 100644
index 0000000000..8b7870f019
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits-precision.js
@@ -0,0 +1,34 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_TRF
+description: >
+ Tests that the digits are determined correctly when specifying
+ pre/post decimal digits.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale,
+ "ar", "de", "th", "ja"
+];
+var numberingSystems = [
+ "arab",
+ "latn",
+ "thai",
+ "hanidec"
+];
+var testData = {
+ // Ref tc39/ecma402#128
+ "12344501000000000000000000000000000": "12344501000000000000000000000000000.0",
+ "-12344501000000000000000000000000000": "-12344501000000000000000000000000000.0"
+};
+
+testNumberFormat(locales, numberingSystems,
+ {useGrouping: false, minimumIntegerDigits: 3, minimumFractionDigits: 1, maximumFractionDigits: 3},
+ testData);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits.js
new file mode 100644
index 0000000000..cb9c20ad6e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-fraction-digits.js
@@ -0,0 +1,57 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_TRF
+description: >
+ Tests that the digits are determined correctly when specifying
+ pre/post decimal digits.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale,
+ "ar", "de", "th", "ja"
+];
+var numberingSystems = [
+ "arab",
+ "latn",
+ "thai",
+ "hanidec"
+];
+var testData = {
+ "0": "000.0",
+ "-0": "-000.0",
+ "123": "123.0",
+ "-123": "-123.0",
+ "12345": "12345.0",
+ "-12345": "-12345.0",
+ "123.45": "123.45",
+ "-123.45": "-123.45",
+ "123.444499": "123.444",
+ "-123.444499": "-123.444",
+ "123.444500": "123.445",
+ "-123.444500": "-123.445",
+ "123.44501": "123.445",
+ "-123.44501": "-123.445",
+ "0.001234": "000.001",
+ "-0.001234": "-000.001",
+ "0.00000000123": "000.0",
+ "-0.00000000123": "-000.0",
+ "0.00000000000000000000000000000123": "000.0",
+ "-0.00000000000000000000000000000123": "-000.0",
+ "1.2": "001.2",
+ "-1.2": "-001.2",
+ "0.0000000012344501": "000.0",
+ "-0.0000000012344501": "-000.0",
+ "123445.01": "123445.01",
+ "-123445.01": "-123445.01",
+};
+
+testNumberFormat(locales, numberingSystems,
+ {useGrouping: false, minimumIntegerDigits: 3, minimumFractionDigits: 1, maximumFractionDigits: 3},
+ testData);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-builtin.js
new file mode 100644
index 0000000000..55f0ce7dd5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-builtin.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_1_a_L15
+description: >
+ Tests that the function returned by
+ Intl.NumberFormat.prototype.format meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+var formatFn = new Intl.NumberFormat().format;
+
+assert.sameValue(Object.prototype.toString.call(formatFn), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatFn),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatFn), Function.prototype);
+
+assert.sameValue(formatFn.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatFn), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-length.js
new file mode 100644
index 0000000000..b2884e9e0d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-length.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ The length of the bound Number Format function is 1.
+info: |
+ get Intl.NumberFormat.prototype.format
+
+ ...
+ 4. If nf.[[BoundFormat]] is undefined, then
+ a. Let F be a new built-in function object as defined in Number Format Functions (11.1.4).
+ b. Let bf be BoundFunctionCreate(F, nf, « »).
+ c. Perform ! DefinePropertyOrThrow(bf, "length", PropertyDescriptor {[[Value]]: 1,
+ [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
+ ...
+
+includes: [propertyHelper.js]
+---*/
+
+var formatFn = new Intl.NumberFormat().format;
+
+verifyProperty(formatFn, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-name.js
new file mode 100644
index 0000000000..4e4805928e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-name.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.format
+description: >
+ The bound NumberFormat format function is an anonymous function.
+info: |
+ 11.4.3 get Intl.NumberFormat.prototype.compare
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in function object, including constructors, has a `name`
+ property whose value is a String. Functions that are identified as
+ anonymous functions use the empty string as the value of the `name`
+ property.
+ Unless otherwise specified, the `name` property of a built-in function
+ object has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*,
+ [[Configurable]]: *true* }.
+includes: [propertyHelper.js]
+---*/
+
+var formatFn = new Intl.NumberFormat().format;
+
+verifyProperty(formatFn, "name", {
+ value: "", writable: false, enumerable: false, configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-property-order.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-property-order.js
new file mode 100644
index 0000000000..91b4884286
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-function-property-order.js
@@ -0,0 +1,18 @@
+// Copyright (C) 2020 ExE Boss. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-createbuiltinfunction
+description: NumberFormat bound format function property order
+info: |
+ Set order: "length", "name"
+includes: [compareArray.js]
+---*/
+
+var formatFn = new Intl.NumberFormat().format;
+
+assert.compareArray(
+ Object.getOwnPropertyNames(formatFn),
+ ['length', 'name']
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-max-min-fraction-significant-digits.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-max-min-fraction-significant-digits.js
new file mode 100644
index 0000000000..f1b2963852
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-max-min-fraction-significant-digits.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Tests that the digits are determined correctly when specifying at same time «"minimumFractionDigits", "maximumFractionDigits", "minimumSignificantDigits", "maximumSignificantDigits"»
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [new Intl.NumberFormat().resolvedOptions().locale, "ar", "de", "th", "ja"];
+var numberingSystems = ["latn", "arab", "thai", "hanidec"];
+
+var nfTestMatrix = [
+ // mnfd & mxfd > mnsd & mxsd
+ [{ useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 4, minimumSignificantDigits: 1, maximumSignificantDigits: 2 }, { 1.23456: "1.2" }],
+ [{ useGrouping: false, minimumFractionDigits: 2, maximumFractionDigits: 4, minimumSignificantDigits: 1, maximumSignificantDigits: 2 }, { 1.23456: "1.2" }],
+ // mnfd & mxfd ∩ mnsd & mxsd
+ [{ useGrouping: false, minimumFractionDigits: 2, maximumFractionDigits: 4, minimumSignificantDigits: 2, maximumSignificantDigits: 3 }, { 1.23456: "1.23" }],
+ // mnfd & mxfd < mnsd & mxsd
+ [{ useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 2, minimumSignificantDigits: 1, maximumSignificantDigits: 4}, { 1.23456: "1.235" }],
+ [{ useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 2, minimumSignificantDigits: 2, maximumSignificantDigits: 4}, { 1.23456: "1.235" }],
+];
+
+nfTestMatrix.forEach((nfTestValues)=>{
+ testNumberFormat(locales, numberingSystems, ...nfTestValues)
+})
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-negative-numbers.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-negative-numbers.js
new file mode 100644
index 0000000000..f572d31997
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-negative-numbers.js
@@ -0,0 +1,23 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_FN_1
+description: >
+ Tests that Intl.NumberFormat.prototype.format doesn't treat all
+ numbers as negative.
+info: |
+ PartitionNumberPattern ( numberFormat, x )
+ 1. If x is not NaN and x < 0 or _x_ is -0, then
+ a. Let _x_ be -_x_.
+ b. Let _pattern_ be _numberFormat_.[[NegativePattern]].
+author: Roozbeh Pournader
+---*/
+
+var formatter = new Intl.NumberFormat();
+
+assert.notSameValue(formatter.format(1), formatter.format(-1), 'Intl.NumberFormat is formatting 1 and -1 the same way.');
+
+assert.notSameValue(formatter.format(0), formatter.format(-0), 'Intl.NumberFormat is formatting 0 and -0 the same way.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-non-finite-numbers.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-non-finite-numbers.js
new file mode 100644
index 0000000000..d6dcae7b65
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-non-finite-numbers.js
@@ -0,0 +1,45 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_FN_2
+description: >
+ Tests that Intl.NumberFormat.prototype.format handles NaN,
+ Infinity, and -Infinity properly.
+author: Roozbeh Pournader
+---*/
+
+// FIXME: We are only listing Numeric_Type=Decimal. May need to add more
+// when the spec clarifies. Current as of Unicode 6.1.
+var hasUnicodeDigits = new RegExp('.*([' +
+ '0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F' +
+ '\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF' +
+ '\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0E50-\u0E59\u0ED0-\u0ED9' +
+ '\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819' +
+ '\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59' +
+ '\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9' +
+ '\uA900-\uA909\uA9D0-\uA9D9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19' +
+ ']|' +
+ '\uD801[\uDCA0-\uDCA9]|' +
+ '\uD804[\uDC66-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9]|' +
+ '\uD805[\uDEC0-\uDEC9]|' +
+ '\uD835[\uDFCE-\uDFFF])');
+
+var formatter = new Intl.NumberFormat();
+var formattedNaN = formatter.format(NaN);
+var formattedInfinity = formatter.format(Infinity);
+var formattedNegativeInfinity = formatter.format(-Infinity);
+
+assert.notSameValue(formattedNaN, formattedInfinity, 'Intl.NumberFormat formats NaN and Infinity the same way.');
+
+assert.notSameValue(formattedNaN, formattedNegativeInfinity, 'Intl.NumberFormat formats NaN and negative Infinity the same way.');
+
+assert.notSameValue(formattedInfinity, formattedNegativeInfinity, 'Intl.NumberFormat formats Infinity and negative Infinity the same way.');
+
+assert.sameValue(hasUnicodeDigits.test(formattedNaN), false, 'Intl.NumberFormat formats NaN using a digit.');
+
+assert.sameValue(hasUnicodeDigits.test(formattedInfinity), false, 'Intl.NumberFormat formats Infinity using a digit.');
+
+assert.sameValue(hasUnicodeDigits.test(formattedNegativeInfinity), false, 'Intl.NumberFormat formats negative Infinity using a digit.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1.js
new file mode 100644
index 0000000000..6b0b209be0
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `1`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 1, maximumFractionDigits: 1},
+ {
+ '1.100': '1.1',
+ '1.125': '1.1',
+ '1.150': '1.2',
+ '1.175': '1.2',
+ '1.200': '1.2',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 1, maximumFractionDigits: 2},
+ {
+ '1.0100': '1.01',
+ '1.0125': '1.01',
+ '1.0150': '1.02',
+ '1.0175': '1.02',
+ '1.0200': '1.02',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-10.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-10.js
new file mode 100644
index 0000000000..efb5a54317
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-10.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `10`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 10, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.100': '1.10',
+ '1.125': '1.10',
+ '1.150': '1.20',
+ '1.175': '1.20',
+ '1.200': '1.20',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 10, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.0100': '1.010',
+ '1.0125': '1.010',
+ '1.0150': '1.020',
+ '1.0175': '1.020',
+ '1.0200': '1.020',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-100.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-100.js
new file mode 100644
index 0000000000..5bd2902968
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-100.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `100`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 100, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.100': '1.100',
+ '1.125': '1.100',
+ '1.150': '1.200',
+ '1.175': '1.200',
+ '1.200': '1.200',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 100, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.0100': '1.0100',
+ '1.0125': '1.0100',
+ '1.0150': '1.0200',
+ '1.0175': '1.0200',
+ '1.0200': '1.0200',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1000.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1000.js
new file mode 100644
index 0000000000..d3bdff64b5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-1000.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `1000`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 1000, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.100': '1.1000',
+ '1.125': '1.1000',
+ '1.150': '1.2000',
+ '1.175': '1.2000',
+ '1.200': '1.2000',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 1000, maximumFractionDigits: 5, minimumFractionDigits: 5},
+ {
+ '1.0100': '1.01000',
+ '1.0125': '1.01000',
+ '1.0150': '1.02000',
+ '1.0175': '1.02000',
+ '1.0200': '1.02000',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2.js
new file mode 100644
index 0000000000..97cc2832c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `2`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2, maximumFractionDigits: 1, minimumFractionDigits: 1},
+ {
+ '1.20': '1.2',
+ '1.25': '1.2',
+ '1.30': '1.4',
+ '1.35': '1.4',
+ '1.40': '1.4',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.020': '1.02',
+ '1.025': '1.02',
+ '1.030': '1.04',
+ '1.035': '1.04',
+ '1.040': '1.04',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-20.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-20.js
new file mode 100644
index 0000000000..68d2725e74
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-20.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `20`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 20, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.20': '1.20',
+ '1.25': '1.20',
+ '1.30': '1.40',
+ '1.35': '1.40',
+ '1.40': '1.40',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 20, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.020': '1.020',
+ '1.025': '1.020',
+ '1.030': '1.040',
+ '1.035': '1.040',
+ '1.040': '1.040',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-200.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-200.js
new file mode 100644
index 0000000000..428fc583b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-200.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `200`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 200, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.20': '1.200',
+ '1.25': '1.200',
+ '1.30': '1.400',
+ '1.35': '1.400',
+ '1.40': '1.400',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 200, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.020': '1.0200',
+ '1.025': '1.0200',
+ '1.030': '1.0400',
+ '1.035': '1.0400',
+ '1.040': '1.0400',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2000.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2000.js
new file mode 100644
index 0000000000..987c6b30fc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2000.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `2000`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2000, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.20': '1.2000',
+ '1.25': '1.2000',
+ '1.30': '1.4000',
+ '1.35': '1.4000',
+ '1.40': '1.4000',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2000, maximumFractionDigits: 5, minimumFractionDigits: 5},
+ {
+ '1.020': '1.02000',
+ '1.025': '1.02000',
+ '1.030': '1.04000',
+ '1.035': '1.04000',
+ '1.040': '1.04000',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-25.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-25.js
new file mode 100644
index 0000000000..a54f963c3e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-25.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `25`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 25, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.2500': '1.25',
+ '1.3125': '1.25',
+ '1.3750': '1.50',
+ '1.4375': '1.50',
+ '1.5000': '1.50',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 25, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.02500': '1.025',
+ '1.03125': '1.025',
+ '1.03750': '1.050',
+ '1.04375': '1.050',
+ '1.05000': '1.050',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-250.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-250.js
new file mode 100644
index 0000000000..6e13faef71
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-250.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `250`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 250, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.2500': '1.250',
+ '1.3125': '1.250',
+ '1.3750': '1.500',
+ '1.4375': '1.500',
+ '1.5000': '1.500',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 250, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.02500': '1.0250',
+ '1.03125': '1.0250',
+ '1.03750': '1.0500',
+ '1.04375': '1.0500',
+ '1.05000': '1.0500',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2500.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2500.js
new file mode 100644
index 0000000000..b7468787c6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-2500.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `2500`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2500, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.2500': '1.2500',
+ '1.3125': '1.2500',
+ '1.3750': '1.5000',
+ '1.4375': '1.5000',
+ '1.5000': '1.5000',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 2500, maximumFractionDigits: 5, minimumFractionDigits: 5},
+ {
+ '1.02500': '1.02500',
+ '1.03125': '1.02500',
+ '1.03750': '1.05000',
+ '1.04375': '1.05000',
+ '1.05000': '1.05000',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5.js
new file mode 100644
index 0000000000..09db9be9a6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `5`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 5, maximumFractionDigits: 1, minimumFractionDigits: 1},
+ {
+ '1.500': '1.5',
+ '1.625': '1.5',
+ '1.750': '2.0',
+ '1.875': '2.0',
+ '2.000': '2.0',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 5, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.0500': '1.05',
+ '1.0625': '1.05',
+ '1.0750': '1.10',
+ '1.0875': '1.10',
+ '1.1000': '1.10',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-50.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-50.js
new file mode 100644
index 0000000000..ef51cfc607
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-50.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `50`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 50, maximumFractionDigits: 2, minimumFractionDigits: 2},
+ {
+ '1.500': '1.50',
+ '1.625': '1.50',
+ '1.750': '2.00',
+ '1.875': '2.00',
+ '2.000': '2.00',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 50, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.0500': '1.050',
+ '1.0625': '1.050',
+ '1.0750': '1.100',
+ '1.0875': '1.100',
+ '1.1000': '1.100',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-500.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-500.js
new file mode 100644
index 0000000000..45c1e67dfe
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-500.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `500`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 500, maximumFractionDigits: 3, minimumFractionDigits: 3},
+ {
+ '1.500': '1.500',
+ '1.625': '1.500',
+ '1.750': '2.000',
+ '1.875': '2.000',
+ '2.000': '2.000',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 500, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.0500': '1.0500',
+ '1.0625': '1.0500',
+ '1.0750': '1.1000',
+ '1.0875': '1.1000',
+ '1.1000': '1.1000',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5000.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5000.js
new file mode 100644
index 0000000000..78eac2c2c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-increment-5000.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: When set to `5000`, roundingIncrement is correctly applied
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 5000, maximumFractionDigits: 4, minimumFractionDigits: 4},
+ {
+ '1.500': '1.5000',
+ '1.625': '1.5000',
+ '1.750': '2.0000',
+ '1.875': '2.0000',
+ '2.000': '2.0000',
+ }
+);
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {roundingIncrement: 5000, maximumFractionDigits: 5, minimumFractionDigits: 5},
+ {
+ '1.0500': '1.05000',
+ '1.0625': '1.05000',
+ '1.0750': '1.10000',
+ '1.0875': '1.10000',
+ '1.1000': '1.10000',
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-ceil.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-ceil.js
new file mode 100644
index 0000000000..3583f38ece
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-ceil.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "ceil", rounding tends toward positive infinity
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'ceil', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.2',
+ '1.15': '1.2',
+ '1.1999': '1.2',
+ '1.25': '1.3',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.1',
+ '-1.1999': '-1.1',
+ '-1.25': '-1.2'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-expand.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-expand.js
new file mode 100644
index 0000000000..1387f1cd77
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-expand.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "expand", rounding tends away from zero
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'expand', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.2',
+ '1.15': '1.2',
+ '1.1999': '1.2',
+ '1.25': '1.3',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.2',
+ '-1.15': '-1.2',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.3'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-floor.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-floor.js
new file mode 100644
index 0000000000..3e2107e388
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-floor.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "floor", rounding tends toward negative infinity
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'floor', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.1',
+ '1.1999': '1.1',
+ '1.25': '1.2',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.2',
+ '-1.15': '-1.2',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.3'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-ceil.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-ceil.js
new file mode 100644
index 0000000000..c3da65ab0e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-ceil.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "halfExpand", rounding tends toward the closest value
+ with ties tending toward positive infinity
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'halfCeil', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.2',
+ '1.1999': '1.2',
+ '1.25': '1.3',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.1',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.2'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-even.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-even.js
new file mode 100644
index 0000000000..0dd7af1626
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-even.js
@@ -0,0 +1,36 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "halfEven", rounding tends toward the closest value
+ with ties tending toward the value with even cardinality
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'halfEven', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.2',
+ '1.1999': '1.2',
+ '1.25': '1.2',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.2',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.2'
+ }
+);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-expand.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-expand.js
new file mode 100644
index 0000000000..d08b6bac84
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-expand.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "halfExpand", rounding tends toward the closest value
+ with ties tending away from zero
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'halfExpand', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.2',
+ '1.1999': '1.2',
+ '1.25': '1.3',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.2',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.3'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-floor.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-floor.js
new file mode 100644
index 0000000000..42a5ce98d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-floor.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "halfFloor", rounding tends toward the closest value
+ with ties tending toward negative infinity
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'halfFloor', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.1',
+ '1.1999': '1.2',
+ '1.25': '1.2',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.2',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.3'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-trunc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-trunc.js
new file mode 100644
index 0000000000..b6babdb903
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-half-trunc.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "halfTrunc", rounding tends toward the closest value
+ with ties tending toward zero
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'halfTrunc', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.1',
+ '1.1999': '1.2',
+ '1.25': '1.2',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.1',
+ '-1.1999': '-1.2',
+ '-1.25': '-1.2'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-trunc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-trunc.js
new file mode 100644
index 0000000000..8c8444ef9c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-mode-trunc.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roundingMode is "trunc", rounding tends toward zero
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingMode: 'trunc', maximumSignificantDigits: 2},
+ {
+ '1.101': '1.1',
+ '1.15': '1.1',
+ '1.1999': '1.1',
+ '1.25': '1.2',
+ '0': '0',
+ '-0': '-0',
+ '-1.101': '-1.1',
+ '-1.15': '-1.1',
+ '-1.1999': '-1.1',
+ '-1.25': '-1.2'
+ }
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-auto.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-auto.js
new file mode 100644
index 0000000000..2199dd6b53
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-auto.js
@@ -0,0 +1,49 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roungingPriority is "auto", the constraint on significant digits is
+ preferred over the constraint on fraction digits
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+// minimumSignificantDigits is less precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'auto', minimumSignificantDigits: 2, minimumFractionDigitsDigits: 2},
+ {'1': '1.0'}
+);
+
+// minimumSignificantDigits is more precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'auto', minimumSignificantDigits: 3, minimumFractionDigitsDigits: 2},
+ {'1': '1.00'}
+);
+
+// maximumSignificantDigits is less precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'auto', maximumSignificantDigits: 2, maximumFractionDigitsDigits: 2},
+ {'1.23': '1.2'}
+);
+
+// maximumSignificantDigits is more precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'auto', maximumSignificantDigits: 3, maximumFractionDigitsDigits: 1},
+ {'1.234': '1.23'}
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-less-precision.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-less-precision.js
new file mode 100644
index 0000000000..2a50ef5cc0
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-less-precision.js
@@ -0,0 +1,49 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roungingPriority is "lessPrecision", the constraint which produces the
+ less precise result is preferred
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+// minimumSignificantDigits is less precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'lessPrecision', minimumSignificantDigits: 2, minimumFractionDigits: 2},
+ {'1': '1.00'}
+);
+
+// minimumSignificantDigits is more precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'lessPrecision', minimumSignificantDigits: 3, minimumFractionDigits: 1},
+ {'1': '1.0'}
+);
+
+// maximumSignificantDigits is less precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'lessPrecision', maximumSignificantDigits: 2, maximumFractionDigits: 2},
+ {'1.23': '1.2'}
+);
+
+// maximumSignificantDigits is more precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'lessPrecision', maximumSignificantDigits: 3, maximumFractionDigits: 1},
+ {'1.234': '1.2'}
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-more-precision.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-more-precision.js
new file mode 100644
index 0000000000..dbe4b3a8a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-rounding-priority-more-precision.js
@@ -0,0 +1,49 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ When roungingPriority is "morePrecision", the constraint which produces the
+ more precise result is preferred
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'
+];
+var numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+// maximumSignificantDigits defaults to 21, beating maximumFractionDigits, which defaults to 3
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'morePrecision', minimumSignificantDigits: 2, minimumFractionDigits: 2},
+ {'1': '1.0'}
+);
+
+// maximumSignificantDigits defaults to 21, beating maximumFractionDigits, which defaults to 3
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'morePrecision', minimumSignificantDigits: 3, minimumFractionDigits: 2},
+ {'1': '1.00'}
+);
+
+// maximumSignificantDigits is less precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'morePrecision', maximumSignificantDigits: 2, maximumFractionDigits: 2},
+ {'1.23': '1.23'}
+);
+
+// maximumSignificantDigits is more precise
+testNumberFormat(
+ locales,
+ numberingSystems,
+ {useGrouping: false, roundingPriority: 'morePrecision', maximumSignificantDigits: 3, maximumFractionDigits: 1},
+ {'1.234': '1.23'}
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits-precision.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits-precision.js
new file mode 100644
index 0000000000..dfb0aa7f59
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits-precision.js
@@ -0,0 +1,34 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_TRP
+description: >
+ Tests that the digits are determined correctly when specifying
+ significant digits.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale,
+ "ar", "de", "th", "ja"
+];
+var numberingSystems = [
+ "arab",
+ "latn",
+ "thai",
+ "hanidec"
+];
+var testData = {
+ // Ref tc39/ecma402#128
+ "123.44500": "123.45",
+ "-123.44500": "-123.45",
+};
+
+testNumberFormat(locales, numberingSystems,
+ {useGrouping: false, minimumSignificantDigits: 3, maximumSignificantDigits: 5},
+ testData);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits.js
new file mode 100644
index 0000000000..d9cc063af5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/format-significant-digits.js
@@ -0,0 +1,57 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_TRP
+description: >
+ Tests that the digits are determined correctly when specifying
+ significant digits.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var locales = [
+ new Intl.NumberFormat().resolvedOptions().locale,
+ "ar", "de", "th", "ja"
+];
+var numberingSystems = [
+ "arab",
+ "latn",
+ "thai",
+ "hanidec"
+];
+var testData = {
+ "0": "0.00",
+ "-0": "-0.00",
+ "123": "123",
+ "-123": "-123",
+ "12345": "12345",
+ "-12345": "-12345",
+ "123.45": "123.45",
+ "-123.45": "-123.45",
+ "123.44499": "123.44",
+ "-123.44499": "-123.44",
+ "123.44501": "123.45",
+ "-123.44501": "-123.45",
+ "0.001234": "0.001234",
+ "-0.001234": "-0.001234",
+ "0.00000000123": "0.00000000123",
+ "-0.00000000123": "-0.00000000123",
+ "0.00000000000000000000000000000123": "0.00000000000000000000000000000123",
+ "-0.00000000000000000000000000000123": "-0.00000000000000000000000000000123",
+ "1.2": "1.20",
+ "-1.2": "-1.20",
+ "0.0000000012344501": "0.0000000012345",
+ "-0.0000000012344501": "-0.0000000012345",
+ "123445.01": "123450",
+ "-123445.01": "-123450",
+ "12344501000000000000000000000000000": "12345000000000000000000000000000000",
+ "-12344501000000000000000000000000000": "-12345000000000000000000000000000000"
+};
+
+testNumberFormat(locales, numberingSystems,
+ {useGrouping: false, minimumSignificantDigits: 3, maximumSignificantDigits: 5},
+ testData);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/length.js
new file mode 100644
index 0000000000..c49e212b7f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/length.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ get Intl.NumberFormat.prototype.format.length is 0.
+info: |
+ get Intl.NumberFormat.prototype.format
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format");
+
+verifyProperty(desc.get, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/name.js
new file mode 100644
index 0000000000..48416d7aa6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/name.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.format
+description: >
+ get Intl.NumberFormat.prototype.format.name is "get format".
+info: |
+ 11.4.3 get Intl.NumberFormat.prototype.format
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format");
+
+verifyProperty(desc.get, "name", {
+ value: "get format",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/no-instanceof.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/no-instanceof.js
new file mode 100644
index 0000000000..03a4f7eac2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/no-instanceof.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ Tests that Intl.NumberFormat.prototype.format calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ UnwrapNumberFormat ( nf )
+
+ 2. If nf does not have an [[InitializedNumberFormat]] internal slot and
+ ? OrdinaryHasInstance(%NumberFormat%, nf) is true, then
+ a. Return ? Get(nf, %Intl%.[[FallbackSymbol]]).
+---*/
+
+const nf = Object.create(Intl.NumberFormat.prototype);
+
+Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+assert.throws(TypeError, () => nf.format);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-de-DE.js
new file mode 100644
index 0000000000..bd33e60f6a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-de-DE.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const nfShort = new Intl.NumberFormat("de-DE", {
+ notation: "compact",
+ compactDisplay: "short",
+});
+assert.sameValue(nfShort.format(987654321), "988\u00a0Mio.");
+assert.sameValue(nfShort.format(98765432), "99\u00a0Mio.");
+assert.sameValue(nfShort.format(98765), "98.765");
+assert.sameValue(nfShort.format(9876), "9876");
+assert.sameValue(nfShort.format(159), "159");
+assert.sameValue(nfShort.format(15.9), "16");
+assert.sameValue(nfShort.format(1.59), "1,6");
+assert.sameValue(nfShort.format(0.159), "0,16");
+assert.sameValue(nfShort.format(0.0159), "0,016");
+assert.sameValue(nfShort.format(0.00159), "0,0016");
+assert.sameValue(nfShort.format(-Infinity), "-∞");
+assert.sameValue(nfShort.format(Infinity), "∞");
+assert.sameValue(nfShort.format(NaN), "NaN");
+
+const nfLong = new Intl.NumberFormat("de-DE", {
+ notation: "compact",
+ compactDisplay: "long",
+});
+assert.sameValue(nfLong.format(987654321), "988 Millionen");
+assert.sameValue(nfLong.format(98765432), "99 Millionen");
+assert.sameValue(nfLong.format(98765), "99 Tausend");
+assert.sameValue(nfLong.format(9876), "9,9 Tausend");
+assert.sameValue(nfLong.format(159), "159");
+assert.sameValue(nfLong.format(15.9), "16");
+assert.sameValue(nfLong.format(1.59), "1,6");
+assert.sameValue(nfLong.format(0.159), "0,16");
+assert.sameValue(nfLong.format(0.0159), "0,016");
+assert.sameValue(nfLong.format(0.00159), "0,0016");
+assert.sameValue(nfLong.format(-Infinity), "-∞");
+assert.sameValue(nfLong.format(Infinity), "∞");
+assert.sameValue(nfLong.format(NaN), "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-en-US.js
new file mode 100644
index 0000000000..09856d66c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-en-US.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const nfShort = new Intl.NumberFormat("en-US", {
+ notation: "compact",
+ compactDisplay: "short",
+});
+assert.sameValue(nfShort.format(987654321), "988M");
+assert.sameValue(nfShort.format(98765432), "99M");
+assert.sameValue(nfShort.format(98765), "99K");
+assert.sameValue(nfShort.format(9876), "9.9K");
+assert.sameValue(nfShort.format(159), "159");
+assert.sameValue(nfShort.format(15.9), "16");
+assert.sameValue(nfShort.format(1.59), "1.6");
+assert.sameValue(nfShort.format(0.159), "0.16");
+assert.sameValue(nfShort.format(0.0159), "0.016");
+assert.sameValue(nfShort.format(0.00159), "0.0016");
+assert.sameValue(nfShort.format(-Infinity), "-∞");
+assert.sameValue(nfShort.format(Infinity), "∞");
+assert.sameValue(nfShort.format(NaN), "NaN");
+
+const nfLong = new Intl.NumberFormat("en-US", {
+ notation: "compact",
+ compactDisplay: "long",
+});
+assert.sameValue(nfLong.format(987654321), "988 million");
+assert.sameValue(nfLong.format(98765432), "99 million");
+assert.sameValue(nfLong.format(98765), "99 thousand");
+assert.sameValue(nfLong.format(9876), "9.9 thousand");
+assert.sameValue(nfLong.format(159), "159");
+assert.sameValue(nfLong.format(15.9), "16");
+assert.sameValue(nfLong.format(1.59), "1.6");
+assert.sameValue(nfLong.format(0.159), "0.16");
+assert.sameValue(nfLong.format(0.0159), "0.016");
+assert.sameValue(nfLong.format(0.00159), "0.0016");
+assert.sameValue(nfLong.format(-Infinity), "-∞");
+assert.sameValue(nfLong.format(Infinity), "∞");
+assert.sameValue(nfLong.format(NaN), "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ja-JP.js
new file mode 100644
index 0000000000..24e6dbfd4f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ja-JP.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const nfShort = new Intl.NumberFormat("ja-JP", {
+ notation: "compact",
+ compactDisplay: "short",
+});
+assert.sameValue(nfShort.format(987654321), "9.9億");
+assert.sameValue(nfShort.format(98765432), "9877万");
+assert.sameValue(nfShort.format(98765), "9.9万");
+assert.sameValue(nfShort.format(9876), "9876");
+assert.sameValue(nfShort.format(159), "159");
+assert.sameValue(nfShort.format(15.9), "16");
+assert.sameValue(nfShort.format(1.59), "1.6");
+assert.sameValue(nfShort.format(0.159), "0.16");
+assert.sameValue(nfShort.format(0.0159), "0.016");
+assert.sameValue(nfShort.format(0.00159), "0.0016");
+assert.sameValue(nfShort.format(-Infinity), "-∞");
+assert.sameValue(nfShort.format(Infinity), "∞");
+assert.sameValue(nfShort.format(NaN), "NaN");
+
+const nfLong = new Intl.NumberFormat("ja-JP", {
+ notation: "compact",
+ compactDisplay: "long",
+});
+assert.sameValue(nfLong.format(987654321), "9.9億");
+assert.sameValue(nfLong.format(98765432), "9877万");
+assert.sameValue(nfLong.format(98765), "9.9万");
+assert.sameValue(nfLong.format(9876), "9876");
+assert.sameValue(nfLong.format(159), "159");
+assert.sameValue(nfLong.format(15.9), "16");
+assert.sameValue(nfLong.format(1.59), "1.6");
+assert.sameValue(nfLong.format(0.159), "0.16");
+assert.sameValue(nfLong.format(0.0159), "0.016");
+assert.sameValue(nfLong.format(0.00159), "0.0016");
+assert.sameValue(nfLong.format(-Infinity), "-∞");
+assert.sameValue(nfLong.format(Infinity), "∞");
+assert.sameValue(nfLong.format(NaN), "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ko-KR.js
new file mode 100644
index 0000000000..d24f3a6e20
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-ko-KR.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const nfShort = new Intl.NumberFormat("ko-KR", {
+ notation: "compact",
+ compactDisplay: "short",
+});
+assert.sameValue(nfShort.format(987654321), "9.9억");
+assert.sameValue(nfShort.format(98765432), "9877만");
+assert.sameValue(nfShort.format(98765), "9.9만");
+assert.sameValue(nfShort.format(9876), "9.9천");
+assert.sameValue(nfShort.format(159), "159");
+assert.sameValue(nfShort.format(15.9), "16");
+assert.sameValue(nfShort.format(1.59), "1.6");
+assert.sameValue(nfShort.format(0.159), "0.16");
+assert.sameValue(nfShort.format(0.0159), "0.016");
+assert.sameValue(nfShort.format(0.00159), "0.0016");
+assert.sameValue(nfShort.format(-Infinity), "-∞");
+assert.sameValue(nfShort.format(Infinity), "∞");
+assert.sameValue(nfShort.format(NaN), "NaN");
+
+const nfLong = new Intl.NumberFormat("ko-KR", {
+ notation: "compact",
+ compactDisplay: "long",
+});
+assert.sameValue(nfLong.format(987654321), "9.9억");
+assert.sameValue(nfLong.format(98765432), "9877만");
+assert.sameValue(nfLong.format(98765), "9.9만");
+assert.sameValue(nfLong.format(9876), "9.9천");
+assert.sameValue(nfLong.format(159), "159");
+assert.sameValue(nfLong.format(15.9), "16");
+assert.sameValue(nfLong.format(1.59), "1.6");
+assert.sameValue(nfLong.format(0.159), "0.16");
+assert.sameValue(nfLong.format(0.0159), "0.016");
+assert.sameValue(nfLong.format(0.00159), "0.0016");
+assert.sameValue(nfLong.format(-Infinity), "-∞");
+assert.sameValue(nfLong.format(Infinity), "∞");
+assert.sameValue(nfLong.format(NaN), "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-zh-TW.js
new file mode 100644
index 0000000000..92e33a7922
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/notation-compact-zh-TW.js
@@ -0,0 +1,47 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+const nfShort = new Intl.NumberFormat("zh-TW", {
+ notation: "compact",
+ compactDisplay: "short",
+});
+assert.sameValue(nfShort.format(987654321), "9.9億");
+assert.sameValue(nfShort.format(98765432), "9877萬");
+assert.sameValue(nfShort.format(98765), "9.9萬");
+assert.sameValue(nfShort.format(9876), "9876");
+assert.sameValue(nfShort.format(159), "159");
+assert.sameValue(nfShort.format(15.9), "16");
+assert.sameValue(nfShort.format(1.59), "1.6");
+assert.sameValue(nfShort.format(0.159), "0.16");
+assert.sameValue(nfShort.format(0.0159), "0.016");
+assert.sameValue(nfShort.format(0.00159), "0.0016");
+assert.sameValue(nfShort.format(-Infinity), "-∞");
+assert.sameValue(nfShort.format(Infinity), "∞");
+assert.sameValue(nfShort.format(NaN), "非數值");
+
+const nfLong = new Intl.NumberFormat("zh-TW", {
+ notation: "compact",
+ compactDisplay: "long",
+});
+assert.sameValue(nfLong.format(987654321), "9.9億");
+assert.sameValue(nfLong.format(98765432), "9877萬");
+assert.sameValue(nfLong.format(98765), "9.9萬");
+assert.sameValue(nfLong.format(9876), "9876");
+assert.sameValue(nfLong.format(159), "159");
+assert.sameValue(nfLong.format(15.9), "16");
+assert.sameValue(nfLong.format(1.59), "1.6");
+assert.sameValue(nfLong.format(0.159), "0.16");
+assert.sameValue(nfLong.format(0.0159), "0.016");
+assert.sameValue(nfLong.format(0.00159), "0.0016");
+assert.sameValue(nfLong.format(-Infinity), "-∞");
+assert.sameValue(nfLong.format(Infinity), "∞");
+assert.sameValue(nfLong.format(NaN), "非數值");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/numbering-systems.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/numbering-systems.js
new file mode 100644
index 0000000000..112657283c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/numbering-systems.js
@@ -0,0 +1,25 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: table-numbering-system-digits
+description: >
+ Tests that Intl.NumberFormat.prototype.format supports all
+ numbering systems with simple digit mappings.
+author: Roozbeh Pournader
+includes: [testIntl.js]
+---*/
+
+for (let [numberingSystem, digits] of Object.entries(numberingSystemDigits)) {
+ let digitList = [...digits];
+ assert.sameValue(digitList.length, 10);
+
+ let nf = new Intl.NumberFormat(undefined, {numberingSystem});
+
+ for (let i = 0; i <= 9; ++i) {
+ assert.sameValue(nf.format(i), digitList[i],
+ `numberingSystem: ${numberingSystem}, digit: ${i}`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/percent-formatter.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/percent-formatter.js
new file mode 100644
index 0000000000..5dc7f70e9e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/percent-formatter.js
@@ -0,0 +1,25 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_FN_3_b
+description: >
+ Tests that Intl.NumberFormat.prototype.format formats percent
+ values properly.
+author: Roozbeh Pournader
+---*/
+
+var numberFormatter = new Intl.NumberFormat();
+var percentFormatter = new Intl.NumberFormat(undefined, {style: 'percent'});
+
+var formattedTwenty = numberFormatter.format(20);
+var formattedTwentyPercent = percentFormatter.format(0.20);
+
+// FIXME: May not work for some theoretical locales where percents and
+// normal numbers are formatted using different numbering systems.
+assert.notSameValue(formattedTwentyPercent.indexOf(formattedTwenty), -1, "Intl.NumberFormat's formatting of 20% does not include a formatting of 20 as a substring.");
+
+// FIXME: Move this to somewhere appropriate
+assert.notSameValue(percentFormatter.format(0.011), percentFormatter.format(0.02), 'Intl.NumberFormat is formatting 1.1% and 2% the same way.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/prop-desc.js
new file mode 100644
index 0000000000..5a2847722d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/prop-desc.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ "format" property of Intl.NumberFormat.prototype.
+info: |
+ get Intl.NumberFormat.prototype.format
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+ If only a get accessor function is described, the set accessor function is the default
+ value, undefined. If only a set accessor is described the get accessor is the default
+ value, undefined.
+
+includes: [propertyHelper.js]
+---*/
+
+var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format");
+
+assert.sameValue(desc.set, undefined);
+assert.sameValue(typeof desc.get, "function");
+
+verifyProperty(Intl.NumberFormat.prototype, "format", {
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-de-DE.js
new file mode 100644
index 0000000000..72d8ccc410
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-de-DE.js
@@ -0,0 +1,62 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-987,00 $",
+ "-0,00 $",
+ "-0,00 $",
+ "0,00 $",
+ "0,00 $",
+ "987,00 $",
+ ],
+ [
+ "always",
+ "-987,00 $",
+ "-0,00 $",
+ "-0,00 $",
+ "+0,00 $",
+ "+0,00 $",
+ "+987,00 $",
+ ],
+ [
+ "never",
+ "987,00 $",
+ "0,00 $",
+ "0,00 $",
+ "0,00 $",
+ "0,00 $",
+ "987,00 $",
+ ],
+ [
+ "exceptZero",
+ "-987,00 $",
+ "0,00 $",
+ "0,00 $",
+ "0,00 $",
+ "0,00 $",
+ "+987,00 $",
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("de-DE", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ assert.sameValue(nf.format(-987), negative);
+ assert.sameValue(nf.format(-0.0001), negativeNearZero);
+ assert.sameValue(nf.format(-0), negativeZero);
+ assert.sameValue(nf.format(0), zero);
+ assert.sameValue(nf.format(0.0001), positiveNearZero);
+ assert.sameValue(nf.format(987), positive);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-en-US.js
new file mode 100644
index 0000000000..72a1a0ba2e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-en-US.js
@@ -0,0 +1,62 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "($987.00)",
+ "($0.00)",
+ "($0.00)",
+ "$0.00",
+ "$0.00",
+ "$987.00",
+ ],
+ [
+ "always",
+ "($987.00)",
+ "($0.00)",
+ "($0.00)",
+ "+$0.00",
+ "+$0.00",
+ "+$987.00",
+ ],
+ [
+ "never",
+ "$987.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$987.00",
+ ],
+ [
+ "exceptZero",
+ "($987.00)",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "+$987.00",
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ assert.sameValue(nf.format(-987), negative);
+ assert.sameValue(nf.format(-0.0001), negativeNearZero);
+ assert.sameValue(nf.format(-0), negativeZero);
+ assert.sameValue(nf.format(0), zero);
+ assert.sameValue(nf.format(0.0001), positiveNearZero);
+ assert.sameValue(nf.format(987), positive);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ja-JP.js
new file mode 100644
index 0000000000..f28ba6c263
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ja-JP.js
@@ -0,0 +1,62 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "($987.00)",
+ "($0.00)",
+ "($0.00)",
+ "$0.00",
+ "$0.00",
+ "$987.00",
+ ],
+ [
+ "always",
+ "($987.00)",
+ "($0.00)",
+ "($0.00)",
+ "+$0.00",
+ "+$0.00",
+ "+$987.00",
+ ],
+ [
+ "never",
+ "$987.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$987.00",
+ ],
+ [
+ "exceptZero",
+ "($987.00)",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "$0.00",
+ "+$987.00",
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ assert.sameValue(nf.format(-987), negative);
+ assert.sameValue(nf.format(-0.0001), negativeNearZero);
+ assert.sameValue(nf.format(-0), negativeZero);
+ assert.sameValue(nf.format(0), zero);
+ assert.sameValue(nf.format(0.0001), positiveNearZero);
+ assert.sameValue(nf.format(987), positive);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ko-KR.js
new file mode 100644
index 0000000000..cfe8f9393a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-ko-KR.js
@@ -0,0 +1,62 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "(US$987.00)",
+ "(US$0.00)",
+ "(US$0.00)",
+ "US$0.00",
+ "US$0.00",
+ "US$987.00",
+ ],
+ [
+ "always",
+ "(US$987.00)",
+ "(US$0.00)",
+ "(US$0.00)",
+ "+US$0.00",
+ "+US$0.00",
+ "+US$987.00",
+ ],
+ [
+ "never",
+ "US$987.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$987.00",
+ ],
+ [
+ "exceptZero",
+ "(US$987.00)",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "+US$987.00",
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ assert.sameValue(nf.format(-987), negative);
+ assert.sameValue(nf.format(-0.0001), negativeNearZero);
+ assert.sameValue(nf.format(-0), negativeZero);
+ assert.sameValue(nf.format(0), zero);
+ assert.sameValue(nf.format(0.0001), positiveNearZero);
+ assert.sameValue(nf.format(987), positive);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-zh-TW.js
new file mode 100644
index 0000000000..677b0b400e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-currency-zh-TW.js
@@ -0,0 +1,62 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "(US$987.00)",
+ "(US$0.00)",
+ "(US$0.00)",
+ "US$0.00",
+ "US$0.00",
+ "US$987.00",
+ ],
+ [
+ "always",
+ "(US$987.00)",
+ "(US$0.00)",
+ "(US$0.00)",
+ "+US$0.00",
+ "+US$0.00",
+ "+US$987.00",
+ ],
+ [
+ "never",
+ "US$987.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$987.00",
+ ],
+ [
+ "exceptZero",
+ "(US$987.00)",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "US$0.00",
+ "+US$987.00",
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("zh-TW", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ assert.sameValue(nf.format(-987), negative);
+ assert.sameValue(nf.format(-0.0001), negativeNearZero);
+ assert.sameValue(nf.format(-0), negativeZero);
+ assert.sameValue(nf.format(0), zero);
+ assert.sameValue(nf.format(0.0001), positiveNearZero);
+ assert.sameValue(nf.format(987), positive);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-de-DE.js
new file mode 100644
index 0000000000..8d7a04d7cd
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-de-DE.js
@@ -0,0 +1,77 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "always",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "+0",
+ "+0",
+ "+987",
+ "+∞",
+ "+NaN",
+ ],
+ [
+ "never",
+ "∞",
+ "987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "exceptZero",
+ "-∞",
+ "-987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "+987",
+ "+∞",
+ "NaN",
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("de-DE", {signDisplay});
+ assert.sameValue(nf.format(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(-987), expected[1], `-987 (${signDisplay})`);
+ assert.sameValue(nf.format(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(-0), expected[3], `-0 (${signDisplay})`);
+ assert.sameValue(nf.format(0), expected[4], `0 (${signDisplay})`);
+ assert.sameValue(nf.format(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(987), expected[6], `987 (${signDisplay})`);
+ assert.sameValue(nf.format(Infinity), expected[7], `Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-en-US.js
new file mode 100644
index 0000000000..7b756562e6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-en-US.js
@@ -0,0 +1,77 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "always",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "+0",
+ "+0",
+ "+987",
+ "+∞",
+ "+NaN",
+ ],
+ [
+ "never",
+ "∞",
+ "987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "exceptZero",
+ "-∞",
+ "-987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "+987",
+ "+∞",
+ "NaN",
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("en-US", {signDisplay});
+ assert.sameValue(nf.format(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(-987), expected[1], `-987 (${signDisplay})`);
+ assert.sameValue(nf.format(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(-0), expected[3], `-0 (${signDisplay})`);
+ assert.sameValue(nf.format(0), expected[4], `0 (${signDisplay})`);
+ assert.sameValue(nf.format(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(987), expected[6], `987 (${signDisplay})`);
+ assert.sameValue(nf.format(Infinity), expected[7], `Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ja-JP.js
new file mode 100644
index 0000000000..8074c621bb
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ja-JP.js
@@ -0,0 +1,77 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "always",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "+0",
+ "+0",
+ "+987",
+ "+∞",
+ "+NaN",
+ ],
+ [
+ "never",
+ "∞",
+ "987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "exceptZero",
+ "-∞",
+ "-987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "+987",
+ "+∞",
+ "NaN",
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("ja-JP", {signDisplay});
+ assert.sameValue(nf.format(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(-987), expected[1], `-987 (${signDisplay})`);
+ assert.sameValue(nf.format(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(-0), expected[3], `-0 (${signDisplay})`);
+ assert.sameValue(nf.format(0), expected[4], `0 (${signDisplay})`);
+ assert.sameValue(nf.format(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(987), expected[6], `987 (${signDisplay})`);
+ assert.sameValue(nf.format(Infinity), expected[7], `Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ko-KR.js
new file mode 100644
index 0000000000..45029e8f89
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-ko-KR.js
@@ -0,0 +1,77 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "always",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "+0",
+ "+0",
+ "+987",
+ "+∞",
+ "+NaN",
+ ],
+ [
+ "never",
+ "∞",
+ "987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "NaN",
+ ],
+ [
+ "exceptZero",
+ "-∞",
+ "-987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "+987",
+ "+∞",
+ "NaN",
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("ko-KR", {signDisplay});
+ assert.sameValue(nf.format(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(-987), expected[1], `-987 (${signDisplay})`);
+ assert.sameValue(nf.format(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(-0), expected[3], `-0 (${signDisplay})`);
+ assert.sameValue(nf.format(0), expected[4], `0 (${signDisplay})`);
+ assert.sameValue(nf.format(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(987), expected[6], `987 (${signDisplay})`);
+ assert.sameValue(nf.format(Infinity), expected[7], `Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-de-DE.js
new file mode 100644
index 0000000000..5f46d481f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-de-DE.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("de-DE", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+assert.sameValue(nf.format(-987), "-987,00 $");
+assert.sameValue(nf.format(-0.0001), "0,00 $");
+assert.sameValue(nf.format(-0), "0,00 $");
+assert.sameValue(nf.format(0), "0,00 $");
+assert.sameValue(nf.format(0.0001), "0,00 $");
+assert.sameValue(nf.format(987), "987,00 $");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-en-US.js
new file mode 100644
index 0000000000..6fca2adf6b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-en-US.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+assert.sameValue(nf.format(-987), "($987.00)");
+assert.sameValue(nf.format(-0.0001), "$0.00");
+assert.sameValue(nf.format(-0), "$0.00");
+assert.sameValue(nf.format(0), "$0.00");
+assert.sameValue(nf.format(0.0001), "$0.00");
+assert.sameValue(nf.format(987), "$987.00");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ja-JP.js
new file mode 100644
index 0000000000..8485f7111e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ja-JP.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+assert.sameValue(nf.format(-987), "($987.00)");
+assert.sameValue(nf.format(-0.0001), "$0.00");
+assert.sameValue(nf.format(-0), "$0.00");
+assert.sameValue(nf.format(0), "$0.00");
+assert.sameValue(nf.format(0.0001), "$0.00");
+assert.sameValue(nf.format(987), "$987.00");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ko-KR.js
new file mode 100644
index 0000000000..f5776b42a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-ko-KR.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+assert.sameValue(nf.format(-987), "(US$987.00)");
+assert.sameValue(nf.format(-0.0001), "US$0.00");
+assert.sameValue(nf.format(-0), "US$0.00");
+assert.sameValue(nf.format(0), "US$0.00");
+assert.sameValue(nf.format(0.0001), "US$0.00");
+assert.sameValue(nf.format(987), "US$987.00");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-zh-TW.js
new file mode 100644
index 0000000000..1ef08d0665
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-currency-zh-TW.js
@@ -0,0 +1,19 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("zh-TW", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+assert.sameValue(nf.format(-987), "(US$987.00)");
+assert.sameValue(nf.format(-0.0001), "US$0.00");
+assert.sameValue(nf.format(-0), "US$0.00");
+assert.sameValue(nf.format(0), "US$0.00");
+assert.sameValue(nf.format(0.0001), "US$0.00");
+assert.sameValue(nf.format(987), "US$987.00");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-de-DE.js
new file mode 100644
index 0000000000..1433dc09be
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-de-DE.js
@@ -0,0 +1,22 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("de-DE", {signDisplay: "negative"});
+assert.sameValue(nf.format(-Infinity), "-∞", "-Infinity");
+assert.sameValue(nf.format(-987), "-987", "-987");
+assert.sameValue(nf.format(-0.0001), "0", "-0.0001");
+assert.sameValue(nf.format(-0), "0", "-0");
+assert.sameValue(nf.format(0), "0", "0");
+assert.sameValue(nf.format(0.0001), "0", "0.0001");
+assert.sameValue(nf.format(987), "987", "987");
+assert.sameValue(nf.format(Infinity), "∞", "Infinity");
+assert.sameValue(nf.format(NaN), "NaN", "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-en-US.js
new file mode 100644
index 0000000000..84cef9d244
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-en-US.js
@@ -0,0 +1,22 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("en-US", {signDisplay: "negative"});
+assert.sameValue(nf.format(-Infinity), "-∞", "-Infinity");
+assert.sameValue(nf.format(-987), "-987", "-987");
+assert.sameValue(nf.format(-0.0001), "0", "-0.0001");
+assert.sameValue(nf.format(-0), "0", "-0");
+assert.sameValue(nf.format(0), "0", "0");
+assert.sameValue(nf.format(0.0001), "0", "0.0001");
+assert.sameValue(nf.format(987), "987", "987");
+assert.sameValue(nf.format(Infinity), "∞", "Infinity");
+assert.sameValue(nf.format(NaN), "NaN", "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ja-JP.js
new file mode 100644
index 0000000000..1b018cb1da
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ja-JP.js
@@ -0,0 +1,22 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("ja-JP", {signDisplay: "negative"});
+assert.sameValue(nf.format(-Infinity), "-∞", "-Infinity");
+assert.sameValue(nf.format(-987), "-987", "-987");
+assert.sameValue(nf.format(-0.0001), "0", "-0.0001");
+assert.sameValue(nf.format(-0), "0", "-0");
+assert.sameValue(nf.format(0), "0", "0");
+assert.sameValue(nf.format(0.0001), "0", "0.0001");
+assert.sameValue(nf.format(987), "987", "987");
+assert.sameValue(nf.format(Infinity), "∞", "Infinity");
+assert.sameValue(nf.format(NaN), "NaN", "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ko-KR.js
new file mode 100644
index 0000000000..c8cff2890f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-ko-KR.js
@@ -0,0 +1,22 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("ko-KR", {signDisplay: "negative"});
+assert.sameValue(nf.format(-Infinity), "-∞", "-Infinity");
+assert.sameValue(nf.format(-987), "-987", "-987");
+assert.sameValue(nf.format(-0.0001), "0", "-0.0001");
+assert.sameValue(nf.format(-0), "0", "-0");
+assert.sameValue(nf.format(0), "0", "0");
+assert.sameValue(nf.format(0.0001), "0", "0.0001");
+assert.sameValue(nf.format(987), "987", "987");
+assert.sameValue(nf.format(Infinity), "∞", "Infinity");
+assert.sameValue(nf.format(NaN), "NaN", "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-zh-TW.js
new file mode 100644
index 0000000000..aadc9e35eb
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-negative-zh-TW.js
@@ -0,0 +1,22 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat("zh-TW", {signDisplay: "negative"});
+assert.sameValue(nf.format(-Infinity), "-∞", "-Infinity");
+assert.sameValue(nf.format(-987), "-987", "-987");
+assert.sameValue(nf.format(-0.0001), "0", "-0.0001");
+assert.sameValue(nf.format(-0), "0", "-0");
+assert.sameValue(nf.format(0), "0", "0");
+assert.sameValue(nf.format(0.0001), "0", "0.0001");
+assert.sameValue(nf.format(987), "987", "987");
+assert.sameValue(nf.format(Infinity), "∞", "Infinity");
+assert.sameValue(nf.format(NaN), "非數值", "NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-rounding.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-rounding.js
new file mode 100644
index 0000000000..726231f81d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-rounding.js
@@ -0,0 +1,20 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const fmt = new Intl.NumberFormat("en-US", {
+ maximumFractionDigits: 1,
+ signDisplay: "exceptZero"
+});
+
+assert.sameValue(fmt.format(0.01), "0");
+assert.sameValue(fmt.format(-0.01), "0");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-zh-TW.js
new file mode 100644
index 0000000000..c16fa79c86
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/signDisplay-zh-TW.js
@@ -0,0 +1,77 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ "auto",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "非數值",
+ ],
+ [
+ "always",
+ "-∞",
+ "-987",
+ "-0",
+ "-0",
+ "+0",
+ "+0",
+ "+987",
+ "+∞",
+ "+非數值",
+ ],
+ [
+ "never",
+ "∞",
+ "987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "987",
+ "∞",
+ "非數值",
+ ],
+ [
+ "exceptZero",
+ "-∞",
+ "-987",
+ "0",
+ "0",
+ "0",
+ "0",
+ "+987",
+ "+∞",
+ "非數值",
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("zh-TW", {signDisplay});
+ assert.sameValue(nf.format(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(-987), expected[1], `-987 (${signDisplay})`);
+ assert.sameValue(nf.format(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(-0), expected[3], `-0 (${signDisplay})`);
+ assert.sameValue(nf.format(0), expected[4], `0 (${signDisplay})`);
+ assert.sameValue(nf.format(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ assert.sameValue(nf.format(987), expected[6], `987 (${signDisplay})`);
+ assert.sameValue(nf.format(Infinity), expected[7], `Infinity (${signDisplay})`);
+ assert.sameValue(nf.format(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/this-value-not-numberformat.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/this-value-not-numberformat.js
new file mode 100644
index 0000000000..bb20845bc9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/this-value-not-numberformat.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: >
+ Tests that Intl.NumberFormat.prototype.format throws a TypeError
+ if called on a non-object value or an object that hasn't been
+ initialized as a NumberFormat.
+---*/
+
+const invalidTargets = [undefined, null, true, 0, 'NumberFormat', [], {}, Symbol()];
+const fn = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, 'format').get;
+
+invalidTargets.forEach(target => {
+ assert.throws(
+ TypeError,
+ () => fn.call(target),
+ `Calling format getter on ${String(target)} should throw a TypeError.`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-de-DE.js
new file mode 100644
index 0000000000..622496c60c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-de-DE.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the unit style.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ -987,
+ {
+ "short": "-987 km/h",
+ "narrow": "-987 km/h",
+ "long": "-987 Kilometer pro Stunde",
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short": "-0,001 km/h",
+ "narrow": "-0,001 km/h",
+ "long": "-0,001 Kilometer pro Stunde",
+ }
+ ],
+ [
+ -0,
+ {
+ "short": "-0 km/h",
+ "narrow": "-0 km/h",
+ "long": "-0 Kilometer pro Stunde",
+ }
+ ],
+ [
+ 0,
+ {
+ "short": "0 km/h",
+ "narrow": "0 km/h",
+ "long": "0 Kilometer pro Stunde",
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short": "0,001 km/h",
+ "narrow": "0,001 km/h",
+ "long": "0,001 Kilometer pro Stunde",
+ }
+ ],
+ [
+ 987,
+ {
+ "short": "987 km/h",
+ "narrow": "987 km/h",
+ "long": "987 Kilometer pro Stunde",
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("de-DE", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ assert.sameValue(nf.format(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-en-US.js
new file mode 100644
index 0000000000..a0d053da30
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-en-US.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the unit style.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ -987,
+ {
+ "short": "-987 km/h",
+ "narrow": "-987km/h",
+ "long": "-987 kilometers per hour",
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short": "-0.001 km/h",
+ "narrow": "-0.001km/h",
+ "long": "-0.001 kilometers per hour",
+ }
+ ],
+ [
+ -0,
+ {
+ "short": "-0 km/h",
+ "narrow": "-0km/h",
+ "long": "-0 kilometers per hour",
+ }
+ ],
+ [
+ 0,
+ {
+ "short": "0 km/h",
+ "narrow": "0km/h",
+ "long": "0 kilometers per hour",
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short": "0.001 km/h",
+ "narrow": "0.001km/h",
+ "long": "0.001 kilometers per hour",
+ }
+ ],
+ [
+ 987,
+ {
+ "short": "987 km/h",
+ "narrow": "987km/h",
+ "long": "987 kilometers per hour",
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("en-US", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ assert.sameValue(nf.format(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ja-JP.js
new file mode 100644
index 0000000000..2fc6c38a37
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ja-JP.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the unit style.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ -987,
+ {
+ "short": "-987 km/h",
+ "narrow": "-987km/h",
+ "long": "時速 -987 キロメートル",
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short": "-0.001 km/h",
+ "narrow": "-0.001km/h",
+ "long": "時速 -0.001 キロメートル",
+ }
+ ],
+ [
+ -0,
+ {
+ "short": "-0 km/h",
+ "narrow": "-0km/h",
+ "long": "時速 -0 キロメートル",
+ }
+ ],
+ [
+ 0,
+ {
+ "short": "0 km/h",
+ "narrow": "0km/h",
+ "long": "時速 0 キロメートル",
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short": "0.001 km/h",
+ "narrow": "0.001km/h",
+ "long": "時速 0.001 キロメートル",
+ }
+ ],
+ [
+ 987,
+ {
+ "short": "987 km/h",
+ "narrow": "987km/h",
+ "long": "時速 987 キロメートル",
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("ja-JP", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ assert.sameValue(nf.format(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ko-KR.js
new file mode 100644
index 0000000000..a7f8e7a908
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-ko-KR.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the unit style.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ -987,
+ {
+ "short": "-987km/h",
+ "narrow": "-987km/h",
+ "long": "시속 -987킬로미터",
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short": "-0.001km/h",
+ "narrow": "-0.001km/h",
+ "long": "시속 -0.001킬로미터",
+ }
+ ],
+ [
+ -0,
+ {
+ "short": "-0km/h",
+ "narrow": "-0km/h",
+ "long": "시속 -0킬로미터",
+ }
+ ],
+ [
+ 0,
+ {
+ "short": "0km/h",
+ "narrow": "0km/h",
+ "long": "시속 0킬로미터",
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short": "0.001km/h",
+ "narrow": "0.001km/h",
+ "long": "시속 0.001킬로미터",
+ }
+ ],
+ [
+ 987,
+ {
+ "short": "987km/h",
+ "narrow": "987km/h",
+ "long": "시속 987킬로미터",
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("ko-KR", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ assert.sameValue(nf.format(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-zh-TW.js
new file mode 100644
index 0000000000..e3c715a30e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/unit-zh-TW.js
@@ -0,0 +1,71 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of the unit style.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+
+const tests = [
+ [
+ -987,
+ {
+ "short": "-987 公里/小時",
+ "narrow": "-987公里/小時",
+ "long": "每小時 -987 公里",
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short": "-0.001 公里/小時",
+ "narrow": "-0.001公里/小時",
+ "long": "每小時 -0.001 公里",
+ }
+ ],
+ [
+ -0,
+ {
+ "short": "-0 公里/小時",
+ "narrow": "-0公里/小時",
+ "long": "每小時 -0 公里",
+ }
+ ],
+ [
+ 0,
+ {
+ "short": "0 公里/小時",
+ "narrow": "0公里/小時",
+ "long": "每小時 0 公里",
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short": "0.001 公里/小時",
+ "narrow": "0.001公里/小時",
+ "long": "每小時 0.001 公里",
+ }
+ ],
+ [
+ 987,
+ {
+ "short": "987 公里/小時",
+ "narrow": "987公里/小時",
+ "long": "每小時 987 公里",
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("zh-TW", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ assert.sameValue(nf.format(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/units-invalid.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/units-invalid.js
new file mode 100644
index 0000000000..904cf29ed9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/units-invalid.js
@@ -0,0 +1,110 @@
+// Copyright 2019 Igalia, S.L., Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of units.
+features: [Intl.NumberFormat-unified]
+---*/
+
+const units = [
+ "acre-foot",
+ "ampere",
+ "arc-minute",
+ "arc-second",
+ "astronomical-unit",
+ "atmosphere",
+ "bushel",
+ "calorie",
+ "carat",
+ "centiliter",
+ "century",
+ "cubic-centimeter",
+ "cubic-foot",
+ "cubic-inch",
+ "cubic-kilometer",
+ "cubic-meter",
+ "cubic-mile",
+ "cubic-yard",
+ "cup-metric",
+ "cup",
+ "day-person",
+ "deciliter",
+ "decimeter",
+ "fathom",
+ "foodcalorie",
+ "furlong",
+ "g-force",
+ "gallon-imperial",
+ "generic",
+ "gigahertz",
+ "gigawatt",
+ "hectoliter",
+ "hectopascal",
+ "hertz",
+ "horsepower",
+ "inch-hg",
+ "joule",
+ "karat",
+ "kelvin",
+ "kilocalorie",
+ "kilohertz",
+ "kilojoule",
+ "kilowatt-hour",
+ "kilowatt",
+ "knot",
+ "light-year",
+ "lux",
+ "megahertz",
+ "megaliter",
+ "megawatt",
+ "metric-ton",
+ "microgram",
+ "micrometer",
+ "milliampere",
+ "millibar",
+ "milligram",
+ "millimeter-of-mercury",
+ "milliwatt",
+ "month-person",
+ "nanometer",
+ "nautical-mile",
+ "ohm",
+ "ounce-troy",
+ "parsec",
+ "permille",
+ "picometer",
+ "pint-metric",
+ "pint",
+ "point",
+ "quart",
+ "radian",
+ "revolution",
+ "square-centimeter",
+ "square-foot",
+ "square-inch",
+ "square-kilometer",
+ "square-meter",
+ "square-mile",
+ "square-yard",
+ "tablespoon",
+ "teaspoon",
+ "ton",
+ "volt",
+ "watt",
+ "week-person",
+ "year-person",
+ "liter-per-100kilometers",
+ "meter-per-second-squared",
+ "mile-per-gallon-imperial",
+ "milligram-per-deciliter",
+ "millimole-per-liter",
+ "part-per-million",
+ "pound-per-square-inch",
+];
+
+for (const unit of units) {
+ assert.throws(RangeError, () => new Intl.NumberFormat(undefined, { style: "unit", unit }), `Throw for ${unit}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/units.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/units.js
new file mode 100644
index 0000000000..8c441008a3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/units.js
@@ -0,0 +1,27 @@
+// Copyright 2019 Igalia, S.L., Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Checks handling of units.
+includes: [testIntl.js]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function check(unit) {
+ const s1 = (123).toLocaleString(undefined, { style: "unit", unit: unit });
+ const s2 = (123).toLocaleString();
+ assert.notSameValue(s1, s2);
+}
+
+const units = allSimpleSanctionedUnits();
+
+for (const simpleUnit of units) {
+ check(simpleUnit);
+ for (const simpleUnit2 of units) {
+ check(simpleUnit + "-per-" + simpleUnit2);
+ check(simpleUnit2 + "-per-" + simpleUnit);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-de-DE.js
new file mode 100644
index 0000000000..a2368e2d21
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-de-DE.js
@@ -0,0 +1,33 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [de-DE]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('de-DE', {});
+
+assert.sameValue(nf.format(100), '100', '(omitted)');
+assert.sameValue(nf.format(1000), '1.000', '(omitted)');
+assert.sameValue(nf.format(10000), '10.000', '(omitted)');
+assert.sameValue(nf.format(100000), '100.000', '(omitted)');
+
+nf = new Intl.NumberFormat('de-DE', {useGrouping: true});
+
+assert.sameValue(nf.format(100), '100', 'true');
+assert.sameValue(nf.format(1000), '1.000', 'true');
+assert.sameValue(nf.format(100000), '100.000', 'true');
+
+nf = new Intl.NumberFormat('de-DE', {useGrouping: false});
+
+assert.sameValue(nf.format(100), '100', 'false');
+assert.sameValue(nf.format(1000), '1000', 'false');
+assert.sameValue(nf.format(10000), '10000', 'false');
+assert.sameValue(nf.format(100000), '100000', 'false');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-IN.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-IN.js
new file mode 100644
index 0000000000..29bc42b207
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-IN.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [en-IN]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('en-IN', {});
+
+assert.sameValue(nf.format(100), '100', '(omitted)');
+assert.sameValue(nf.format(1000), '1,000', '(omitted)');
+assert.sameValue(nf.format(10000), '10,000', '(omitted)');
+assert.sameValue(nf.format(100000), '1,00,000', '(omitted)');
+
+nf = new Intl.NumberFormat('en-IN', {useGrouping: true});
+
+assert.sameValue(nf.format(100), '100', 'true');
+assert.sameValue(nf.format(1000), '1,000', 'true');
+assert.sameValue(nf.format(10000), '10,000', 'true');
+assert.sameValue(nf.format(100000), '1,00,000', 'true');
+
+nf = new Intl.NumberFormat('en-IN', {useGrouping: false});
+
+assert.sameValue(nf.format(100), '100', 'false');
+assert.sameValue(nf.format(1000), '1000', 'false');
+assert.sameValue(nf.format(10000), '10000', 'false');
+assert.sameValue(nf.format(100000), '100000', 'false');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-US.js
new file mode 100644
index 0000000000..3baaafb8bc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-en-US.js
@@ -0,0 +1,34 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [en-US]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('en-US', {});
+
+assert.sameValue(nf.format(100), '100', '(omitted)');
+assert.sameValue(nf.format(1000), '1,000', '(omitted)');
+assert.sameValue(nf.format(10000), '10,000', '(omitted)');
+assert.sameValue(nf.format(100000), '100,000', '(omitted)');
+
+nf = new Intl.NumberFormat('en-US', {useGrouping: true});
+
+assert.sameValue(nf.format(100), '100', 'true');
+assert.sameValue(nf.format(1000), '1,000', 'true');
+assert.sameValue(nf.format(10000), '10,000', 'true');
+assert.sameValue(nf.format(100000), '100,000', 'true');
+
+nf = new Intl.NumberFormat('en-US', {useGrouping: false});
+
+assert.sameValue(nf.format(100), '100', 'false');
+assert.sameValue(nf.format(1000), '1000', 'false');
+assert.sameValue(nf.format(10000), '10000', 'false');
+assert.sameValue(nf.format(100000), '100000', 'false');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-de-DE.js
new file mode 100644
index 0000000000..eb7270ac2b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-de-DE.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-v3]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('de-DE', {useGrouping: 'always'});
+
+assert.sameValue(nf.format(100), '100', '"always"');
+assert.sameValue(nf.format(1000), '1.000', '"always"');
+assert.sameValue(nf.format(10000), '10.000', '"always"');
+assert.sameValue(nf.format(100000), '100.000', '"always"');
+
+nf = new Intl.NumberFormat('de-DE', {useGrouping: 'min2'});
+
+assert.sameValue(nf.format(100), '100', '"min2"');
+assert.sameValue(nf.format(1000), '1000', '"min2"');
+assert.sameValue(nf.format(10000), '10.000', '"min2"');
+assert.sameValue(nf.format(100000), '100.000', '"min2"');
+
+nf = new Intl.NumberFormat('de-DE', {notation: 'compact'});
+
+assert.sameValue(nf.format(100), '100', 'notation: "compact"');
+assert.sameValue(nf.format(1000), '1000', 'notation: "compact"');
+assert.sameValue(nf.format(10000), '10.000', 'notation: "compact"');
+assert.sameValue(nf.format(100000), '100.000', 'notation: "compact"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js
new file mode 100644
index 0000000000..34445338e2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [en-IN]
+features: [Intl.NumberFormat-v3]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('en-IN', {useGrouping: 'always'});
+
+assert.sameValue(nf.format(100), '100', '"always"');
+assert.sameValue(nf.format(1000), '1,000', '"always"');
+assert.sameValue(nf.format(10000), '10,000', '"always"');
+assert.sameValue(nf.format(100000), '1,00,000', '"always"');
+
+nf = new Intl.NumberFormat('en-IN', {useGrouping: 'min2'});
+
+assert.sameValue(nf.format(100), '100', '"min2"');
+assert.sameValue(nf.format(1000), '1000', '"min2"');
+assert.sameValue(nf.format(10000), '10,000', '"min2"');
+assert.sameValue(nf.format(100000), '1,00,000', '"min2"');
+
+nf = new Intl.NumberFormat('en-IN', {notation: 'compact'});
+
+assert.sameValue(nf.format(100), '100', 'notation: "compact"');
+assert.sameValue(nf.format(1000), '1T', 'notation: "compact"');
+assert.sameValue(nf.format(10000), '10T', 'notation: "compact"');
+assert.sameValue(nf.format(100000), '1L', 'notation: "compact"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-US.js
new file mode 100644
index 0000000000..0846461275
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/useGrouping-extended-en-US.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: |
+ Checks handling of the useGrouping option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+var nf;
+
+nf = new Intl.NumberFormat('en-US', {useGrouping: 'always'});
+
+assert.sameValue(nf.format(100), '100', '"always"');
+assert.sameValue(nf.format(1000), '1,000', '"always"');
+assert.sameValue(nf.format(10000), '10,000', '"always"');
+assert.sameValue(nf.format(100000), '100,000', '"always"');
+
+nf = new Intl.NumberFormat('en-US', {useGrouping: 'min2'});
+
+assert.sameValue(nf.format(100), '100', '"min2"');
+assert.sameValue(nf.format(1000), '1000', '"min2"');
+assert.sameValue(nf.format(10000), '10,000', '"min2"');
+assert.sameValue(nf.format(100000), '100,000', '"min2"');
+
+nf = new Intl.NumberFormat('en-US', {notation: 'compact'});
+
+assert.sameValue(nf.format(100), '100', 'notation: "compact"');
+assert.sameValue(nf.format(1000), '1K', 'notation: "compact"');
+assert.sameValue(nf.format(10000), '10K', 'notation: "compact"');
+assert.sameValue(nf.format(100000), '100K', 'notation: "compact"');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-arg-coerced-to-number.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-arg-coerced-to-number.js
new file mode 100644
index 0000000000..da25c9c5f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-arg-coerced-to-number.js
@@ -0,0 +1,26 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.3.2_1_a_ii
+description: >
+ Tests that Intl.NumberFormat.prototype.format converts other
+ types to numbers.
+author: Roozbeh Pournader
+---*/
+
+var formatter = new Intl.NumberFormat();
+var testData = [undefined, null, true, '0.6666666', {valueOf: function () { return '0.1234567';}}];
+var number;
+var i, input, correctResult, result;
+
+for (i in testData) {
+ input = testData[i];
+ number = +input;
+ correctResult = formatter.format(number);
+
+ result = formatter.format(input);
+ assert.sameValue(result, correctResult, 'Intl.NumberFormat does not convert other types to numbers. Input: "' + input + '".');
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-decimal-string.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-decimal-string.js
new file mode 100644
index 0000000000..f80bc2fb9b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-decimal-string.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-number-format-functions
+description: >
+ Intl.NumberFormat.prototype.format converts its argument (called value) to a
+ number using ToIntlMathematicalValue.
+features: [Intl.NumberFormat-v3]
+locale: [en-US]
+---*/
+
+var nf = new Intl.NumberFormat('en-US', {maximumFractionDigits: 20});
+
+// The value 100,000 should only be interpreted as infinity if the input is the
+// string "Infinity".
+assert.sameValue(nf.format('100000'), '100,000');
+// The value -100,000 should only be interpreted as negative infinity if the
+// input is the string "-Infinity".
+assert.sameValue(nf.format('-100000'), '-100,000');
+
+assert.sameValue(nf.format('1.0000000000000001'), '1.0000000000000001');
+assert.sameValue(nf.format('-1.0000000000000001'), '-1.0000000000000001');
+assert.sameValue(nf.format('987654321987654321'), '987,654,321,987,654,321');
+assert.sameValue(nf.format('-987654321987654321'), '-987,654,321,987,654,321');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-tonumber.js b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-tonumber.js
new file mode 100644
index 0000000000..3785b29103
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/format/value-tonumber.js
@@ -0,0 +1,46 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-number-format-functions
+description: >
+ Tests that Intl.NumberFormat.prototype.format converts its argument
+ (called value) to a number using ToNumber (7.1.3).
+info: |
+ 11.1.4Number Format Functions
+
+ 4. Let x be ? ToNumber(value).
+features: [Symbol]
+---*/
+
+const toNumberResults = [
+ [undefined, NaN],
+ [null, +0],
+ [true, 1],
+ [false, 0],
+ ['42', 42],
+ ['foo', NaN],
+ ['Infinity', Infinity],
+ ['-Infinity', -Infinity]
+];
+
+const nf = new Intl.NumberFormat();
+
+toNumberResults.forEach(pair => {
+ const [value, result] = pair;
+ assert.sameValue(nf.format(value), nf.format(result));
+});
+
+let count = 0;
+const dummy = {};
+dummy[Symbol.toPrimitive] = hint => (hint === 'number' ? ++count : NaN);
+assert.sameValue(nf.format(dummy), nf.format(count));
+assert.sameValue(count, 1);
+
+assert.throws(
+ TypeError,
+ () => nf.format(Symbol()),
+ "ToNumber(arg) throws a TypeError when arg is of type 'Symbol'"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/argument-to-Intlmathematicalvalue-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/argument-to-Intlmathematicalvalue-throws.js
new file mode 100644
index 0000000000..46d6f63e31
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/argument-to-Intlmathematicalvalue-throws.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRange
+description: >
+ "formatRange" basic tests when argument cannot be converted using ToIntlMathematicalValue
+info: |
+ Intl.NumberFormat.prototype.formatRange( start, end )
+ (...)
+ 4. Let x be ? ToIntlMathematicalValue(start).
+ 5. Let y be ? ToIntlMathematicalValue(end).
+features: [Intl.NumberFormat-v3]
+---*/
+
+
+const nf = new Intl.NumberFormat();
+
+// Throw if arguments cannot be cast using the method ToIntlMathematicalValue
+assert.throws(TypeError, () => { nf.formatRange(Symbol(102), 201) });
+assert.throws(TypeError, () => { nf.formatRange(102,Symbol(201)) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/builtin.js
new file mode 100644
index 0000000000..91baf121e3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/builtin.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+ Tests that the Intl.NumberFormat.prototype.formatRange function meets the
+ requirements for built-in objects defined by the ECMAScript Language
+ Specification.
+includes: [isConstructor.js]
+features: [Reflect.construct,Intl.NumberFormat-v3]
+---*/
+
+const formatRange = Intl.NumberFormat.prototype.formatRange;
+
+assert.sameValue(Object.prototype.toString.call(formatRange), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatRange),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatRange), Function.prototype);
+
+assert.sameValue(formatRange.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatRange), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/en-US.js
new file mode 100644
index 0000000000..9eabe00c26
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/en-US.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRange
+description: Basic tests for the en-US output of formatRange()
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+// Basic example test en-US
+const nf = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ maximumFractionDigits: 0,
+});
+
+assert.sameValue(nf.formatRange(3, 5), "$3 – $5");
+assert.sameValue(nf.formatRange(2.9, 3.1), "~$3");
+
+
+// Basic example test en-US using signDisplay to always
+const nf2 = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ signDisplay: "always",
+});
+
+assert.sameValue(nf2.formatRange(2.9, 3.1), "+$2.90–3.10");
+
+// Basic example test en-US string formatting
+const nf3 = new Intl.NumberFormat("en-US");
+const string1 = "987654321987654321";
+const string2 = "987654321987654322";
+
+assert.sameValue(nf3.formatRange(string1, string2), "987,654,321,987,654,321–987,654,321,987,654,322");
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/invoked-as-func.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/invoked-as-func.js
new file mode 100644
index 0000000000..b08abc7d26
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/invoked-as-func.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-NumberFormat
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.NumberFormat.prototype.formatRange(start, end )
+ (...)
+ 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]])
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]])
+let f = nf['formatRange'];
+
+assert.sameValue(typeof f, 'function');
+assert.throws(TypeError, () => { f(1, 23) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/length.js
new file mode 100644
index 0000000000..aefcbeb93e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/length.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatRange.length.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+verifyProperty(Intl.NumberFormat.prototype.formatRange, 'length', {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/name.js
new file mode 100644
index 0000000000..ce60f9e67b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/name.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatRange.name value and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+verifyProperty(Intl.NumberFormat.prototype.formatRange, 'name', {
+ value: 'formatRange',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/nan-arguments-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/nan-arguments-throws.js
new file mode 100644
index 0000000000..c686cb84a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/nan-arguments-throws.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRange
+description: >
+ "formatRange" Throws a RangeError if some of arguments is cast to NaN
+info: |
+ Intl.NumberFormat.prototype.formatRange( start, end )
+ (...)
+ 6. Return ? FormatNumericRange(nf, x, y).
+
+ FormatNumericRange( numberFormat, x, y )
+ 1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
+
+ PartitionNumberRangePattern( numberFormat, x, y )
+ 1. If x is NaN or y is NaN, throw a RangeError exception.
+features: [Intl.NumberFormat-v3]
+---*/
+
+
+const nf = new Intl.NumberFormat();
+
+// If x or y is NaN ..., throw a RangeError exception.
+assert.throws(RangeError, () => { nf.formatRange(NaN, 23) });
+assert.throws(RangeError, () => { nf.formatRange(12, NaN) });
+assert.throws(RangeError, () => { nf.formatRange(NaN, -23) });
+assert.throws(RangeError, () => { nf.formatRange(-12, NaN) });
+assert.throws(RangeError, () => { nf.formatRange(NaN, NaN) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/prop-desc.js
new file mode 100644
index 0000000000..a0348dc446
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/prop-desc.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Property type and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.sameValue(
+ typeof Intl.NumberFormat.prototype.formatRange,
+ 'function',
+ '`typeof Intl.NumberFormat.prototype.formatRange` is `function`'
+);
+
+verifyProperty(Intl.NumberFormat.prototype, 'formatRange', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/pt-PT.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/pt-PT.js
new file mode 100644
index 0000000000..dfb1d4a531
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/pt-PT.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRange
+description: Basic tests for the pt-PT output of formatRange()
+locale: [pt-PT]
+features: [Intl.NumberFormat-v3]
+---*/
+
+// Basic example test pt-PT
+const nf = new Intl.NumberFormat("pt-PT", {
+ style: "currency",
+ currency: "EUR",
+ maximumFractionDigits: 0,
+});
+
+assert.sameValue(nf.formatRange(3, 5), "3 - 5\u00a0€");
+assert.sameValue(nf.formatRange(2.9, 3.1), "~3\u00a0€");
+
+
+// Basic example test pt-PT using signDisplay to always
+const nf2 = new Intl.NumberFormat("pt-PT", {
+ style: "currency",
+ currency: "EUR",
+ signDisplay: "always",
+});
+
+assert.sameValue(nf2.formatRange(2.9, 3.1), "+2,90 - 3,10\u00a0€");
+
+// Basic example test pt-PT string formatting
+const nf3 = new Intl.NumberFormat("pt-PT");
+const string1 = "987654321987654321";
+const string2 = "987654321987654322";
+
+assert.sameValue(nf3.formatRange(string1, string2), "987\u00a0654\u00a0321\u00a0987\u00a0654\u00a0321 - 987\u00a0654\u00a0321\u00a0987\u00a0654\u00a0322");
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/undefined-arguments-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/undefined-arguments-throws.js
new file mode 100644
index 0000000000..a9fbd4f712
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/undefined-arguments-throws.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.formatRange
+description: >
+ "formatRange" basic tests when arguments are undefined throw a TypeError exception.
+info: |
+ Intl.NumberFormat.prototype.formatRange ( start, end )
+ (...)
+ 3. If start is undefined or end is undefined, throw a TypeError exception.
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// If arguments are undefined throw a TypeError exception.
+assert.throws(TypeError, () => { nf.formatRange(undefined, 23) });
+assert.throws(TypeError, () => { nf.formatRange(1,undefined) });
+assert.throws(TypeError, () => { nf.formatRange(undefined, undefined)});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/x-greater-than-y-not-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/x-greater-than-y-not-throws.js
new file mode 100644
index 0000000000..42cb8e1c66
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRange/x-greater-than-y-not-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2022 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.formatRange
+description: >
+ "formatRange" basic tests when argument x > y, BigInt included and covers PartitionNumberRangePattern return a string.
+info: |
+ 1.1.21 PartitionNumberRangePattern( numberFormat, x, y )
+ (...)
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// If x > y, return a string.
+assert.sameValue(typeof nf.formatRange(23, 12), "string",
+ "should return string not throw RangeError");
+
+// If x > y, return a string and both x and y are bigint.
+assert.sameValue(typeof nf.formatRange(23n, 12n), "string",
+ "should return string not throw RangeError");
+//if y is -∞, return a string.
+assert.sameValue(typeof nf.formatRange(23, -Infinity), "string",
+ "should return string not throw RangeError");
+//if y is -0 and x ≥ 0, return a string.
+assert.sameValue(typeof nf.formatRange(23, -0), "string",
+ "should return string not throw RangeError");
+assert.sameValue(typeof nf.formatRange(0, -0), "string",
+ "should return string not throw RangeError");
+
+// if y is a mathematical value, return a string.
+assert.sameValue(typeof nf.formatRange(Infinity, 23), "string",
+ "should return string not throw RangeError");
+// if y is -∞, return a string.
+assert.sameValue(typeof nf.formatRange(Infinity, -Infinity), "string",
+ "should return string not throw RangeError");
+// if y is -0, return a string.
+assert.sameValue(typeof nf.formatRange(Infinity, -0), "string",
+ "should return string not throw RangeError");
+
+// if y is a mathematical value and y < 0, return a string.
+assert.sameValue(typeof nf.formatRange(-0, -1), "string",
+ "should return string not throw RangeError");
+// if y is -∞, return a string.
+assert.sameValue(typeof nf.formatRange(-0, -Infinity), "string",
+ "should return string not throw RangeError");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/argument-to-Intlmathematicalvalue-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/argument-to-Intlmathematicalvalue-throws.js
new file mode 100644
index 0000000000..ab2f111db7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/argument-to-Intlmathematicalvalue-throws.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRangeToParts
+description: >
+ "formatRangeToParts" basic tests when argument cannot be converted using ToIntlMathematicalValue
+info: |
+ Intl.NumberFormat.prototype.formatRangeToParts( start, end )
+ (...)
+ 4. Let x be ? ToIntlMathematicalValue(start).
+ 5. Let y be ? ToIntlMathematicalValue(end).
+features: [Intl.NumberFormat-v3]
+---*/
+
+
+const nf = new Intl.NumberFormat();
+
+// Throw if arguments cannot be cast using the method ToIntlMathematicalValue
+assert.throws(TypeError, () => { nf.formatRangeToParts(Symbol(102), 201) });
+assert.throws(TypeError, () => { nf.formatRangeToParts(102,Symbol(201)) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/builtin.js
new file mode 100644
index 0000000000..d39a1c5819
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/builtin.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+ Tests that the Intl.NumberFormat.prototype.formatRangeToParts function meets the
+ requirements for built-in objects defined by the ECMAScript Language
+ Specification.
+includes: [isConstructor.js]
+features: [Reflect.construct,Intl.NumberFormat-v3]
+---*/
+
+const formatRangeToParts = Intl.NumberFormat.prototype.formatRangeToParts;
+
+assert.sameValue(Object.prototype.toString.call(formatRangeToParts), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(formatRangeToParts),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(formatRangeToParts), Function.prototype);
+
+assert.sameValue(formatRangeToParts.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(formatRangeToParts), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/en-US.js
new file mode 100644
index 0000000000..a23cd17f40
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/en-US.js
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRangeToParts
+description: Basic tests for the en-US output of formatRangeToParts()
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+includes: [propertyHelper.js]
+---*/
+
+// Utils functions
+function* zip(a, b) {
+ assert.sameValue(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ // assertions
+ assert.sameValue(actualEntry.type, expectedEntry.type, `type for entry ${i}`);
+ assert.sameValue(actualEntry.value, expectedEntry.value, `value for entry ${i}`);
+ assert.sameValue(actualEntry.source, expectedEntry.source, `source for entry ${i}`);
+
+ // 1.1.25_4.a Let O be ObjectCreate(%ObjectPrototype%).
+ assert.sameValue(Object.getPrototypeOf(actualEntry), Object.prototype, `prototype for entry ${i}`);
+ // 1.1.25_4.b Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]])
+ verifyProperty(actualEntry, 'type', { enumerable: true, writable: true, configurable: true });
+ // 1.1.25_4.c Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
+ verifyProperty(actualEntry, 'value', { enumerable: true, writable: true, configurable: true });
+ // 1.1.25_4.d Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
+ verifyProperty(actualEntry, 'source', { enumerable: true, writable: true, configurable: true });
+ }
+}
+
+// Basic example test en-US
+const nf = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ maximumFractionDigits: 0,
+});
+
+compare(nf.formatRangeToParts(3, 5), [
+ {type: "currency", value: "$", source: "startRange"},
+ {type: "integer", value: "3", source: "startRange"},
+ {type: "literal", value: " – ", source: "shared"},
+ {type: "currency", value: "$", source: "endRange"},
+ {type: "integer", value: "5", source: "endRange"}
+]);
+
+compare(nf.formatRangeToParts(1, 1), [
+ {type: 'approximatelySign', value: '~', source: 'shared'},
+ {type: 'currency', value: '$', source: 'shared'},
+ {type: 'integer', value: '1', source: 'shared'}
+]);
+
+compare(nf.formatRangeToParts(2.999, 3.001), [
+ {type: 'approximatelySign', value: '~', source: 'shared'},
+ {type: 'currency', value: '$', source: 'shared'},
+ {type: 'integer', value: '3', source: 'shared'}
+]);
+
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func.js
new file mode 100644
index 0000000000..f3e41989b0
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-NumberFormat
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.NumberFormat.prototype.formatRangeToParts ( start, end )
+ (...)
+ 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]])
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]])
+let f = nf['formatRangeToParts'];
+
+assert.sameValue(typeof f, 'function');
+assert.throws(TypeError, () => { f(1, 23) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/length.js
new file mode 100644
index 0000000000..9cc15dcc59
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/length.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatRangeToParts.length.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+verifyProperty(Intl.NumberFormat.prototype.formatRangeToParts, 'length', {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/name.js
new file mode 100644
index 0000000000..425bc569f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/name.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatRangeToParts.name value and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+verifyProperty(Intl.NumberFormat.prototype.formatRangeToParts, 'name', {
+ value: 'formatRangeToParts',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws.js
new file mode 100644
index 0000000000..8dacae8be1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws.js
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat-formatRangeToParts
+description: >
+ "formatRangeToParts" Throws a RangeError if some of arguments is cast to NaN
+info: |
+ Intl.NumberFormat.prototype.formatRangeToParts( start, end )
+ (...)
+ 6. Return ? FormatNumericRangeToParts(nf, x, y).
+
+ FormatNumericRangeToParts( numberFormat, x, y )
+ 1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
+
+ PartitionNumberRangePattern( numberFormat, x, y )
+ 1. If x is NaN or y is NaN, throw a RangeError exception.
+features: [Intl.NumberFormat-v3]
+---*/
+
+
+const nf = new Intl.NumberFormat();
+
+// If x or y is NaN ..., throw a RangeError exception.
+assert.throws(RangeError, () => { nf.formatRangeToParts(NaN, 23) });
+assert.throws(RangeError, () => { nf.formatRangeToParts(12, NaN) });
+assert.throws(RangeError, () => { nf.formatRangeToParts(NaN, -23) });
+assert.throws(RangeError, () => { nf.formatRangeToParts(-12, NaN) });
+assert.throws(RangeError, () => { nf.formatRangeToParts(NaN, NaN) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/prop-desc.js
new file mode 100644
index 0000000000..32f0bd5364
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/prop-desc.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Property type and descriptor.
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.sameValue(
+ typeof Intl.NumberFormat.prototype.formatRangeToParts,
+ 'function',
+ '`typeof Intl.NumberFormat.prototype.formatRangeToParts` is `function`'
+);
+
+verifyProperty(Intl.NumberFormat.prototype, 'formatRangeToParts', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/undefined-arguments-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/undefined-arguments-throws.js
new file mode 100644
index 0000000000..3906806b5f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/undefined-arguments-throws.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.formatRangeToParts
+description: >
+ "formatRangeToParts" basic tests when arguments are undefined throw a TypeError exception.
+info: |
+ Intl.NumberFormat.prototype.formatRangeToParts ( start, end )
+ (...)
+ 3. If start is undefined or end is undefined, throw a TypeError exception.
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// If arguments are undefined throw a TypeError exception.
+assert.throws(TypeError, () => { nf.formatRangeToParts(undefined, 23) });
+assert.throws(TypeError, () => { nf.formatRangeToParts(1,undefined) });
+assert.throws(TypeError, () => { nf.formatRangeToParts(undefined, undefined)});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-not-throws.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-not-throws.js
new file mode 100644
index 0000000000..f9b49cc604
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-not-throws.js
@@ -0,0 +1,49 @@
+// Copyright 2022 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.formatRangeToParts
+description: >
+ "formatRangeToParts" basic tests when argument x > y, BigInt included and covers PartitionNumberRangePattern return a object.
+info: |
+ 1.1.21 PartitionNumberRangePattern( numberFormat, x, y )
+ (...)
+features: [Intl.NumberFormat-v3]
+---*/
+
+const nf = new Intl.NumberFormat();
+
+// If x > y, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(23, 12), "object",
+ "should return object not throw RangeError");
+
+// If x > y, return a object and both x and y are bigint.
+assert.sameValue(typeof nf.formatRangeToParts(23n, 12n), "object",
+ "should return object not throw RangeError");
+//if y is -∞, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(23, -Infinity), "object",
+ "should return object not throw RangeError");
+//if y is -0 and x ≥ 0, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(23, -0), "object",
+ "should return object not throw RangeError");
+assert.sameValue(typeof nf.formatRangeToParts(0, -0), "object",
+ "should return object not throw RangeError");
+
+// if y is a mathematical value, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(Infinity, 23), "object",
+ "should return object not throw RangeError");
+// if y is -∞, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(Infinity, -Infinity), "object",
+ "should return object not throw RangeError");
+// if y is -0, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(Infinity, -0), "object",
+ "should return object not throw RangeError");
+
+// if y is a mathematical value and y < 0, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(-0, -1), "object",
+ "should return object not throw RangeError");
+// if y is -∞, return a object.
+assert.sameValue(typeof nf.formatRangeToParts(-0, -Infinity), "object",
+ "should return object not throw RangeError");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/default-parameter.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/default-parameter.js
new file mode 100644
index 0000000000..c2ec726586
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/default-parameter.js
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 Josh Wolfe. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Intl.NumberFormat.prototype.formatToParts called with no parameters
+info: |
+ Intl.NumberFormat.prototype.formatToParts ([ value ])
+
+ 3. If value is not provided, let value be undefined.
+---*/
+
+var nf = new Intl.NumberFormat();
+
+const implicit = nf.formatToParts();
+const explicit = nf.formatToParts(undefined);
+
+// In most locales this is string "NaN", but there are exceptions, cf. "ليس رقم"
+// in Arabic, "epäluku" in Finnish, "не число" in Russian, "son emas" in Uzbek etc.
+const resultNaN = nf.format(NaN);
+const result = [{ type: 'nan', value: resultNaN }];
+
+assert(
+ partsEquals(implicit, explicit),
+ 'formatToParts() should be equivalent to formatToParts(undefined)'
+);
+
+assert(
+ partsEquals(implicit, result),
+ 'Both implicit and explicit calls should have the correct result'
+);
+
+function partsEquals(parts1, parts2) {
+ if (parts1.length !== parts2.length) return false;
+ for (var i = 0; i < parts1.length; i++) {
+ var part1 = parts1[i];
+ var part2 = parts2[i];
+ if (part1.type !== part2.type) return false;
+ if (part1.value !== part2.value) return false;
+ }
+ return true;
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-de-DE.js
new file mode 100644
index 0000000000..b325432203
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-de-DE.js
@@ -0,0 +1,88 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the engineering and scientific notations.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ 0.000345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"6"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":","},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"4"}],
+ ],
+ [
+ 0.345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":","},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 3.45,
+ [{"type":"integer","value":"3"},{"type":"decimal","value":","},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":","},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ ],
+ [
+ 34.5,
+ [{"type":"integer","value":"34"},{"type":"decimal","value":","},{"type":"fraction","value":"5"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":","},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 543,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":","},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"2"}],
+ ],
+ [
+ 5430,
+ [{"type":"integer","value":"5"},{"type":"decimal","value":","},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":","},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ ],
+ [
+ 543000,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":","},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ 543211.1,
+ [{"type":"integer","value":"543"},{"type":"decimal","value":","},{"type":"fraction","value":"211"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":","},{"type":"fraction","value":"432"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("de-DE", { notation: "engineering" }));
+ verifyFormatParts(nfEngineering.formatToParts(number), engineering, `${number} - engineering`);
+ const nfScientific = (new Intl.NumberFormat("de-DE", { notation: "scientific" }));
+ verifyFormatParts(nfScientific.formatToParts(number), scientific, `${number} - scientific`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-en-US.js
new file mode 100644
index 0000000000..c05ad26852
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-en-US.js
@@ -0,0 +1,88 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the engineering and scientific notations.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ 0.000345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"6"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"4"}],
+ ],
+ [
+ 0.345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 3.45,
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ ],
+ [
+ 34.5,
+ [{"type":"integer","value":"34"},{"type":"decimal","value":"."},{"type":"fraction","value":"5"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 543,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"2"}],
+ ],
+ [
+ 5430,
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ ],
+ [
+ 543000,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ 543211.1,
+ [{"type":"integer","value":"543"},{"type":"decimal","value":"."},{"type":"fraction","value":"211"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"432"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("en-US", { notation: "engineering" }));
+ verifyFormatParts(nfEngineering.formatToParts(number), engineering, `${number} - engineering`);
+ const nfScientific = (new Intl.NumberFormat("en-US", { notation: "scientific" }));
+ verifyFormatParts(nfScientific.formatToParts(number), scientific, `${number} - scientific`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ja-JP.js
new file mode 100644
index 0000000000..4bf8ecaea7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ja-JP.js
@@ -0,0 +1,88 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the engineering and scientific notations.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ 0.000345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"6"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"4"}],
+ ],
+ [
+ 0.345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 3.45,
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ ],
+ [
+ 34.5,
+ [{"type":"integer","value":"34"},{"type":"decimal","value":"."},{"type":"fraction","value":"5"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 543,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"2"}],
+ ],
+ [
+ 5430,
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ ],
+ [
+ 543000,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ 543211.1,
+ [{"type":"integer","value":"543"},{"type":"decimal","value":"."},{"type":"fraction","value":"211"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"432"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("ja-JP", { notation: "engineering" }));
+ verifyFormatParts(nfEngineering.formatToParts(number), engineering, `${number} - engineering`);
+ const nfScientific = (new Intl.NumberFormat("ja-JP", { notation: "scientific" }));
+ verifyFormatParts(nfScientific.formatToParts(number), scientific, `${number} - scientific`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ko-KR.js
new file mode 100644
index 0000000000..fa6cc8b5c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-ko-KR.js
@@ -0,0 +1,88 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the engineering and scientific notations.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ 0.000345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"6"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"4"}],
+ ],
+ [
+ 0.345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 3.45,
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ ],
+ [
+ 34.5,
+ [{"type":"integer","value":"34"},{"type":"decimal","value":"."},{"type":"fraction","value":"5"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 543,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"2"}],
+ ],
+ [
+ 5430,
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ ],
+ [
+ 543000,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ 543211.1,
+ [{"type":"integer","value":"543"},{"type":"decimal","value":"."},{"type":"fraction","value":"211"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"432"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("ko-KR", { notation: "engineering" }));
+ verifyFormatParts(nfEngineering.formatToParts(number), engineering, `${number} - engineering`);
+ const nfScientific = (new Intl.NumberFormat("ko-KR", { notation: "scientific" }));
+ verifyFormatParts(nfScientific.formatToParts(number), scientific, `${number} - scientific`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-zh-TW.js
new file mode 100644
index 0000000000..86b59171cc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/engineering-scientific-zh-TW.js
@@ -0,0 +1,88 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the engineering and scientific notations.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ 0.000345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"6"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"4"}],
+ ],
+ [
+ 0.345,
+ [{"type":"integer","value":"345"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentMinusSign","value":"-"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 3.45,
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ ],
+ [
+ 34.5,
+ [{"type":"integer","value":"34"},{"type":"decimal","value":"."},{"type":"fraction","value":"5"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"3"},{"type":"decimal","value":"."},{"type":"fraction","value":"45"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"1"}],
+ ],
+ [
+ 543,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"0"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"2"}],
+ ],
+ [
+ 5430,
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ ],
+ [
+ 543000,
+ [{"type":"integer","value":"543"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"43"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ 543211.1,
+ [{"type":"integer","value":"543"},{"type":"decimal","value":"."},{"type":"fraction","value":"211"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"3"}],
+ [{"type":"integer","value":"5"},{"type":"decimal","value":"."},{"type":"fraction","value":"432"},{"type":"exponentSeparator","value":"E"},{"type":"exponentInteger","value":"5"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"非數值"}],
+ [{"type":"nan","value":"非數值"}],
+ ],
+];
+
+for (const [number, engineering, scientific] of tests) {
+ const nfEngineering = (new Intl.NumberFormat("zh-TW", { notation: "engineering" }));
+ verifyFormatParts(nfEngineering.formatToParts(number), engineering, `${number} - engineering`);
+ const nfScientific = (new Intl.NumberFormat("zh-TW", { notation: "scientific" }));
+ verifyFormatParts(nfScientific.formatToParts(number), scientific, `${number} - scientific`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/length.js
new file mode 100644
index 0000000000..b21012bb80
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/length.js
@@ -0,0 +1,16 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatToParts.length.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype.formatToParts, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/main.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/main.js
new file mode 100644
index 0000000000..d6ce6de200
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/main.js
@@ -0,0 +1,67 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Tests for existance and behavior of Intl.NumberFormat.prototype.formatToParts
+---*/
+
+function reduce(parts) {
+ return parts.map(part => part.value).join('');
+}
+
+function compareFTPtoFormat(locales, options, value) {
+ const nf = new Intl.NumberFormat(locales, options);
+ assert.sameValue(
+ nf.format(value),
+ reduce(nf.formatToParts(value)),
+ `Expected the same value for value ${value},
+ locales: ${locales} and options: ${options}`
+ );
+}
+
+const num1 = 123456.789;
+const num2 = 0.123;
+
+compareFTPtoFormat();
+compareFTPtoFormat('pl');
+compareFTPtoFormat(['pl']);
+compareFTPtoFormat([]);
+compareFTPtoFormat(['de'], undefined, 0);
+compareFTPtoFormat(['de'], undefined, -10);
+compareFTPtoFormat(['de'], undefined, 25324234235);
+compareFTPtoFormat(['de'], undefined, num1);
+compareFTPtoFormat(['de'], {
+ style: 'percent'
+}, num2);
+compareFTPtoFormat(['de'], {
+ style: 'currency',
+ currency: 'EUR'
+}, num1);
+compareFTPtoFormat(['de'], {
+ style: 'currency',
+ currency: 'EUR',
+ currencyDisplay: 'code'
+}, num1);
+compareFTPtoFormat(['de'], {
+ useGrouping: true
+}, num1);
+compareFTPtoFormat(['de'], {
+ useGrouping: false
+}, num1);
+compareFTPtoFormat(['de'], {
+ minimumIntegerDigits: 2
+}, num2);
+compareFTPtoFormat(['de'], {
+ minimumFractionDigits: 6
+}, num2);
+compareFTPtoFormat(['de'], {
+ maximumFractionDigits: 1
+}, num2);
+compareFTPtoFormat(['de'], {
+ maximumSignificantDigits: 3
+}, num1);
+compareFTPtoFormat(['de'], {
+ maximumSignificantDigits: 5
+}, num1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/name.js
new file mode 100644
index 0000000000..dcfd9b5cec
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/name.js
@@ -0,0 +1,16 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+description: Intl.NumberFormat.prototype.formatToParts.name value and descriptor.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype.formatToParts, "name", {
+ value: "formatToParts",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-de-DE.js
new file mode 100644
index 0000000000..a5de211642
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-de-DE.js
@@ -0,0 +1,94 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+const tests = [
+ [
+ 987654321,
+ [{"type":"integer","value":"988"},{"type":"literal","value":"\u00a0"},{"type":"compact","value":"Mio."}],
+ [{"type":"integer","value":"988"},{"type":"literal","value":" "},{"type":"compact","value":"Millionen"}],
+ ],
+ [
+ 98765432,
+ [{"type":"integer","value":"99"},{"type":"literal","value":"\u00a0"},{"type":"compact","value":"Mio."}],
+ [{"type":"integer","value":"99"},{"type":"literal","value":" "},{"type":"compact","value":"Millionen"}],
+ ],
+ [
+ 98765,
+ [{"type":"integer","value":"98"},{"type":"group","value":"."},{"type":"integer","value":"765"}],
+ [{"type":"integer","value":"99"},{"type":"literal","value":" "},{"type":"compact","value":"Tausend"}],
+ ],
+ [
+ 9876,
+ [{"type":"integer","value":"9876"}],
+ [{"type":"integer","value":"9"},{"type":"decimal","value":","},{"type":"fraction","value":"9"},{"type":"literal","value":" "},{"type":"compact","value":"Tausend"}],
+ ],
+ [
+ 159,
+ [{"type":"integer","value":"159"}],
+ ],
+ [
+ 15.9,
+ [{"type":"integer","value":"16"}],
+ ],
+ [
+ 1.59,
+ [{"type":"integer","value":"1"},{"type":"decimal","value":","},{"type":"fraction","value":"6"}],
+ ],
+ [
+ 0.159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"16"}],
+ ],
+ [
+ 0.0159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"016"}],
+ ],
+ [
+ 0.00159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"0016"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, short, long = short] of tests) {
+ const nfShort = new Intl.NumberFormat("de-DE", {
+ notation: "compact",
+ compactDisplay: "short",
+ });
+ verifyFormatParts(nfShort.formatToParts(number), short, `Compact short: ${number}`);
+
+ const nfLong = new Intl.NumberFormat("de-DE", {
+ notation: "compact",
+ compactDisplay: "long",
+ });
+ verifyFormatParts(nfLong.formatToParts(number), long, `Compact long: ${number}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-en-US.js
new file mode 100644
index 0000000000..84ea596f29
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-en-US.js
@@ -0,0 +1,94 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+const tests = [
+ [
+ 987654321,
+ [{"type":"integer","value":"988"},{"type":"compact","value":"M"}],
+ [{"type":"integer","value":"988"},{"type":"literal","value":" "},{"type":"compact","value":"million"}],
+ ],
+ [
+ 98765432,
+ [{"type":"integer","value":"99"},{"type":"compact","value":"M"}],
+ [{"type":"integer","value":"99"},{"type":"literal","value":" "},{"type":"compact","value":"million"}],
+ ],
+ [
+ 98765,
+ [{"type":"integer","value":"99"},{"type":"compact","value":"K"}],
+ [{"type":"integer","value":"99"},{"type":"literal","value":" "},{"type":"compact","value":"thousand"}],
+ ],
+ [
+ 9876,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"K"}],
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"literal","value":" "},{"type":"compact","value":"thousand"}],
+ ],
+ [
+ 159,
+ [{"type":"integer","value":"159"}],
+ ],
+ [
+ 15.9,
+ [{"type":"integer","value":"16"}],
+ ],
+ [
+ 1.59,
+ [{"type":"integer","value":"1"},{"type":"decimal","value":"."},{"type":"fraction","value":"6"}],
+ ],
+ [
+ 0.159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"16"}],
+ ],
+ [
+ 0.0159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"016"}],
+ ],
+ [
+ 0.00159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"0016"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, short, long = short] of tests) {
+ const nfShort = new Intl.NumberFormat("en-US", {
+ notation: "compact",
+ compactDisplay: "short",
+ });
+ verifyFormatParts(nfShort.formatToParts(number), short, `Compact short: ${number}`);
+
+ const nfLong = new Intl.NumberFormat("en-US", {
+ notation: "compact",
+ compactDisplay: "long",
+ });
+ verifyFormatParts(nfLong.formatToParts(number), long, `Compact long: ${number}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ja-JP.js
new file mode 100644
index 0000000000..9299f9f7bc
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ja-JP.js
@@ -0,0 +1,90 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+const tests = [
+ [
+ 987654321,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"億"}],
+ ],
+ [
+ 98765432,
+ [{"type":"integer","value":"9877"},{"type":"compact","value":"万"}],
+ ],
+ [
+ 98765,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"万"}],
+ ],
+ [
+ 9876,
+ [{"type":"integer","value":"9876"}],
+ ],
+ [
+ 159,
+ [{"type":"integer","value":"159"}],
+ ],
+ [
+ 15.9,
+ [{"type":"integer","value":"16"}],
+ ],
+ [
+ 1.59,
+ [{"type":"integer","value":"1"},{"type":"decimal","value":"."},{"type":"fraction","value":"6"}],
+ ],
+ [
+ 0.159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"16"}],
+ ],
+ [
+ 0.0159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"016"}],
+ ],
+ [
+ 0.00159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"0016"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, short, long = short] of tests) {
+ const nfShort = new Intl.NumberFormat("ja-JP", {
+ notation: "compact",
+ compactDisplay: "short",
+ });
+ verifyFormatParts(nfShort.formatToParts(number), short, `Compact short: ${number}`);
+
+ const nfLong = new Intl.NumberFormat("ja-JP", {
+ notation: "compact",
+ compactDisplay: "long",
+ });
+ verifyFormatParts(nfLong.formatToParts(number), long, `Compact long: ${number}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ko-KR.js
new file mode 100644
index 0000000000..067c9a3b32
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-ko-KR.js
@@ -0,0 +1,90 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+const tests = [
+ [
+ 987654321,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"억"}],
+ ],
+ [
+ 98765432,
+ [{"type":"integer","value":"9877"},{"type":"compact","value":"만"}],
+ ],
+ [
+ 98765,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"만"}],
+ ],
+ [
+ 9876,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"천"}],
+ ],
+ [
+ 159,
+ [{"type":"integer","value":"159"}],
+ ],
+ [
+ 15.9,
+ [{"type":"integer","value":"16"}],
+ ],
+ [
+ 1.59,
+ [{"type":"integer","value":"1"},{"type":"decimal","value":"."},{"type":"fraction","value":"6"}],
+ ],
+ [
+ 0.159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"16"}],
+ ],
+ [
+ 0.0159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"016"}],
+ ],
+ [
+ 0.00159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"0016"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [number, short, long = short] of tests) {
+ const nfShort = new Intl.NumberFormat("ko-KR", {
+ notation: "compact",
+ compactDisplay: "short",
+ });
+ verifyFormatParts(nfShort.formatToParts(number), short, `Compact short: ${number}`);
+
+ const nfLong = new Intl.NumberFormat("ko-KR", {
+ notation: "compact",
+ compactDisplay: "long",
+ });
+ verifyFormatParts(nfLong.formatToParts(number), long, `Compact long: ${number}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-zh-TW.js
new file mode 100644
index 0000000000..0b0ab99b1a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/notation-compact-zh-TW.js
@@ -0,0 +1,90 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the compactDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+const tests = [
+ [
+ 987654321,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"億"}],
+ ],
+ [
+ 98765432,
+ [{"type":"integer","value":"9877"},{"type":"compact","value":"萬"}],
+ ],
+ [
+ 98765,
+ [{"type":"integer","value":"9"},{"type":"decimal","value":"."},{"type":"fraction","value":"9"},{"type":"compact","value":"萬"}],
+ ],
+ [
+ 9876,
+ [{"type":"integer","value":"9876"}],
+ ],
+ [
+ 159,
+ [{"type":"integer","value":"159"}],
+ ],
+ [
+ 15.9,
+ [{"type":"integer","value":"16"}],
+ ],
+ [
+ 1.59,
+ [{"type":"integer","value":"1"},{"type":"decimal","value":"."},{"type":"fraction","value":"6"}],
+ ],
+ [
+ 0.159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"16"}],
+ ],
+ [
+ 0.0159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"016"}],
+ ],
+ [
+ 0.00159,
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"0016"}],
+ ],
+ [
+ -Infinity,
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ ],
+ [
+ Infinity,
+ [{"type":"infinity","value":"∞"}],
+ ],
+ [
+ NaN,
+ [{"type":"nan","value":"非數值"}],
+ ],
+];
+
+for (const [number, short, long = short] of tests) {
+ const nfShort = new Intl.NumberFormat("zh-TW", {
+ notation: "compact",
+ compactDisplay: "short",
+ });
+ verifyFormatParts(nfShort.formatToParts(number), short, `Compact short: ${number}`);
+
+ const nfLong = new Intl.NumberFormat("zh-TW", {
+ notation: "compact",
+ compactDisplay: "long",
+ });
+ verifyFormatParts(nfLong.formatToParts(number), long, `Compact long: ${number}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js
new file mode 100644
index 0000000000..02b1cb5c42
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the percent style and unit.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nfStyle = new Intl.NumberFormat("en-US", { style: "percent" });
+verifyFormatParts(nfStyle.formatToParts(-123), [
+ {"type":"minusSign","value":"-"},
+ {"type":"integer","value":"12"},
+ {"type":"group","value":","},
+ {"type":"integer","value":"300"},
+ {"type":"percentSign","value":"%"},
+], "style");
+
+const nfUnit = new Intl.NumberFormat("en-US", { style: "unit", unit: "percent" });
+verifyFormatParts(nfUnit.formatToParts(-123), [
+ {"type":"minusSign","value":"-"},
+ {"type":"integer","value":"123"},
+ {"type":"unit","value":"%"},
+], "unit");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/prop-desc.js
new file mode 100644
index 0000000000..e667e3bb32
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/prop-desc.js
@@ -0,0 +1,41 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: >
+ "formatToParts" property of Intl.NumberFormat.prototype.
+info: |
+ 11.4.4 Intl.NumberFormat.prototype.formatToParts
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every accessor property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+ If only a get accessor function is described, the set accessor function is the default
+ value, undefined. If only a set accessor is described the get accessor is the default
+ value, undefined.
+
+includes: [propertyHelper.js]
+---*/
+
+assert.sameValue(
+ typeof Intl.NumberFormat.prototype.formatToParts,
+ 'function',
+ '`typeof Intl.NumberFormat.prototype.formatToParts` is `function`'
+);
+
+verifyProperty(Intl.NumberFormat.prototype, "formatToParts", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-de-DE.js
new file mode 100644
index 0000000000..d2f61becd6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-de-DE.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ ],
+ [
+ "never",
+ [{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("de-DE", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ verifyFormatParts(nf.formatToParts(-987), negative, "negative");
+ verifyFormatParts(nf.formatToParts(-0.0001), negativeNearZero, "negativeNearZero");
+ verifyFormatParts(nf.formatToParts(-0), negativeZero, "negativeZero");
+ verifyFormatParts(nf.formatToParts(0), zero, "zero");
+ verifyFormatParts(nf.formatToParts(0.0001), positiveNearZero, "positiveNearZero");
+ verifyFormatParts(nf.formatToParts(987), positive, "positive");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-en-US.js
new file mode 100644
index 0000000000..9905591d77
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-en-US.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "always",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "never",
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ verifyFormatParts(nf.formatToParts(-987), negative, "negative");
+ verifyFormatParts(nf.formatToParts(-0.0001), negativeNearZero, "negativeNearZero");
+ verifyFormatParts(nf.formatToParts(-0), negativeZero, "negativeZero");
+ verifyFormatParts(nf.formatToParts(0), zero, "zero");
+ verifyFormatParts(nf.formatToParts(0.0001), positiveNearZero, "positiveNearZero");
+ verifyFormatParts(nf.formatToParts(987), positive, "positive");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ja-JP.js
new file mode 100644
index 0000000000..67d4db79b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ja-JP.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "always",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "never",
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ verifyFormatParts(nf.formatToParts(-987), negative, "negative");
+ verifyFormatParts(nf.formatToParts(-0.0001), negativeNearZero, "negativeNearZero");
+ verifyFormatParts(nf.formatToParts(-0), negativeZero, "negativeZero");
+ verifyFormatParts(nf.formatToParts(0), zero, "zero");
+ verifyFormatParts(nf.formatToParts(0.0001), positiveNearZero, "positiveNearZero");
+ verifyFormatParts(nf.formatToParts(987), positive, "positive");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ko-KR.js
new file mode 100644
index 0000000000..226394e2ef
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-ko-KR.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "always",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "never",
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ verifyFormatParts(nf.formatToParts(-987), negative, "negative");
+ verifyFormatParts(nf.formatToParts(-0.0001), negativeNearZero, "negativeNearZero");
+ verifyFormatParts(nf.formatToParts(-0), negativeZero, "negativeZero");
+ verifyFormatParts(nf.formatToParts(0), zero, "zero");
+ verifyFormatParts(nf.formatToParts(0.0001), positiveNearZero, "positiveNearZero");
+ verifyFormatParts(nf.formatToParts(987), positive, "positive");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-zh-TW.js
new file mode 100644
index 0000000000..27027aee33
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-currency-zh-TW.js
@@ -0,0 +1,72 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "always",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "never",
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ [{"type":"plusSign","value":"+"},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ ],
+];
+
+for (const [signDisplay, negative, negativeNearZero, negativeZero, zero, positiveNearZero, positive] of tests) {
+ const nf = new Intl.NumberFormat("zh-TW", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay });
+ verifyFormatParts(nf.formatToParts(-987), negative, "negative");
+ verifyFormatParts(nf.formatToParts(-0.0001), negativeNearZero, "negativeNearZero");
+ verifyFormatParts(nf.formatToParts(-0), negativeZero, "negativeZero");
+ verifyFormatParts(nf.formatToParts(0), zero, "zero");
+ verifyFormatParts(nf.formatToParts(0.0001), positiveNearZero, "positiveNearZero");
+ verifyFormatParts(nf.formatToParts(987), positive, "positive");
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-de-DE.js
new file mode 100644
index 0000000000..f0e57379f6
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-de-DE.js
@@ -0,0 +1,87 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"plusSign","value":"+"},{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "never",
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("de-DE", {signDisplay});
+ verifyFormatParts(nf.formatToParts(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-987), expected[1], `-987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0), expected[3], `-0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0), expected[4], `0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(987), expected[6], `987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(Infinity), expected[7], `Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-en-US.js
new file mode 100644
index 0000000000..6fdf3bb5a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-en-US.js
@@ -0,0 +1,87 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"plusSign","value":"+"},{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "never",
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("en-US", {signDisplay});
+ verifyFormatParts(nf.formatToParts(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-987), expected[1], `-987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0), expected[3], `-0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0), expected[4], `0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(987), expected[6], `987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(Infinity), expected[7], `Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ja-JP.js
new file mode 100644
index 0000000000..41748c1dab
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ja-JP.js
@@ -0,0 +1,87 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"plusSign","value":"+"},{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "never",
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("ja-JP", {signDisplay});
+ verifyFormatParts(nf.formatToParts(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-987), expected[1], `-987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0), expected[3], `-0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0), expected[4], `0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(987), expected[6], `987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(Infinity), expected[7], `Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ko-KR.js
new file mode 100644
index 0000000000..6a751269f3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-ko-KR.js
@@ -0,0 +1,87 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"plusSign","value":"+"},{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "never",
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"NaN"}],
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("ko-KR", {signDisplay});
+ verifyFormatParts(nf.formatToParts(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-987), expected[1], `-987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0), expected[3], `-0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0), expected[4], `0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(987), expected[6], `987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(Infinity), expected[7], `Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-de-DE.js
new file mode 100644
index 0000000000..f3ef8fb416
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-de-DE.js
@@ -0,0 +1,55 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nf = new Intl.NumberFormat("de-DE", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+
+verifyFormatParts(
+ nf.formatToParts(-987),
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "negative"
+);
+verifyFormatParts(
+ nf.formatToParts(-0.0001),
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "negativeNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(-0),
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "negativeZero"
+);
+verifyFormatParts(
+ nf.formatToParts(0),
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "zero"
+);
+verifyFormatParts(
+ nf.formatToParts(0.0001),
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "positiveNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(987),
+ [{"type":"integer","value":"987"},{"type":"decimal","value":","},{"type":"fraction","value":"00"},{"type":"literal","value":" "},{"type":"currency","value":"$"}],
+ "positive"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-en-US.js
new file mode 100644
index 0000000000..09b9e26d0f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-en-US.js
@@ -0,0 +1,55 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nf = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+
+verifyFormatParts(
+ nf.formatToParts(-987),
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ "negative"
+);
+verifyFormatParts(
+ nf.formatToParts(-0.0001),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(-0),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeZero"
+);
+verifyFormatParts(
+ nf.formatToParts(0),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "zero"
+);
+verifyFormatParts(
+ nf.formatToParts(0.0001),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positiveNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(987),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positive"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ja-JP.js
new file mode 100644
index 0000000000..a651563b23
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ja-JP.js
@@ -0,0 +1,55 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nf = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+
+verifyFormatParts(
+ nf.formatToParts(-987),
+ [{"type":"literal","value":"("},{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ "negative"
+);
+verifyFormatParts(
+ nf.formatToParts(-0.0001),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(-0),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeZero"
+);
+verifyFormatParts(
+ nf.formatToParts(0),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "zero"
+);
+verifyFormatParts(
+ nf.formatToParts(0.0001),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positiveNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(987),
+ [{"type":"currency","value":"$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positive"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ko-KR.js
new file mode 100644
index 0000000000..c7263383b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-ko-KR.js
@@ -0,0 +1,55 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nf = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+
+verifyFormatParts(
+ nf.formatToParts(-987),
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ "negative"
+);
+verifyFormatParts(
+ nf.formatToParts(-0.0001),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(-0),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeZero"
+);
+verifyFormatParts(
+ nf.formatToParts(0),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "zero"
+);
+verifyFormatParts(
+ nf.formatToParts(0.0001),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positiveNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(987),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positive"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-zh-TW.js
new file mode 100644
index 0000000000..aa2ca4a810
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-currency-zh-TW.js
@@ -0,0 +1,55 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const nf = new Intl.NumberFormat("zh-TW", { style: "currency", currency: "USD", currencySign: "accounting", signDisplay: "negative" });
+
+verifyFormatParts(
+ nf.formatToParts(-987),
+ [{"type":"literal","value":"("},{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"},{"type":"literal","value":")"}],
+ "negative"
+);
+verifyFormatParts(
+ nf.formatToParts(-0.0001),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(-0),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "negativeZero"
+);
+verifyFormatParts(
+ nf.formatToParts(0),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "zero"
+);
+verifyFormatParts(
+ nf.formatToParts(0.0001),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positiveNearZero"
+);
+verifyFormatParts(
+ nf.formatToParts(987),
+ [{"type":"currency","value":"US$"},{"type":"integer","value":"987"},{"type":"decimal","value":"."},{"type":"fraction","value":"00"}],
+ "positive"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-de-DE.js
new file mode 100644
index 0000000000..ea25559606
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-de-DE.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [de-DE]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const signDisplay = "negative";
+const nf = new Intl.NumberFormat("de-DE", {signDisplay: "negative"});
+
+verifyFormatParts(nf.formatToParts(-Infinity), [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}], `-Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-987), [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}], `-987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0.0001), [{"type":"integer","value":"0"}], `-0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0), [{"type":"integer","value":"0"}], `-0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0), [{"type":"integer","value":"0"}], `0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0.0001), [{"type":"integer","value":"0"}], `0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(987), [{"type":"integer","value":"987"}], `987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(Infinity), [{"type":"infinity","value":"∞"}], `Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(NaN), [{"type":"nan","value":"NaN"}], `NaN (${signDisplay})`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-en-US.js
new file mode 100644
index 0000000000..07b2bee627
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-en-US.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const signDisplay = "negative";
+const nf = new Intl.NumberFormat("en-US", {signDisplay: "negative"});
+
+verifyFormatParts(nf.formatToParts(-Infinity), [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}], `-Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-987), [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}], `-987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0.0001), [{"type":"integer","value":"0"}], `-0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0), [{"type":"integer","value":"0"}], `-0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0), [{"type":"integer","value":"0"}], `0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0.0001), [{"type":"integer","value":"0"}], `0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(987), [{"type":"integer","value":"987"}], `987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(Infinity), [{"type":"infinity","value":"∞"}], `Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(NaN), [{"type":"nan","value":"NaN"}], `NaN (${signDisplay})`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ja-JP.js
new file mode 100644
index 0000000000..fe9da55796
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ja-JP.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ja-JP]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const signDisplay = "negative";
+const nf = new Intl.NumberFormat("ja-JP", {signDisplay: "negative"});
+
+verifyFormatParts(nf.formatToParts(-Infinity), [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}], `-Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-987), [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}], `-987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0.0001), [{"type":"integer","value":"0"}], `-0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0), [{"type":"integer","value":"0"}], `-0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0), [{"type":"integer","value":"0"}], `0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0.0001), [{"type":"integer","value":"0"}], `0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(987), [{"type":"integer","value":"987"}], `987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(Infinity), [{"type":"infinity","value":"∞"}], `Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(NaN), [{"type":"nan","value":"NaN"}], `NaN (${signDisplay})`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ko-KR.js
new file mode 100644
index 0000000000..faa534d236
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-ko-KR.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [ko-KR]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const signDisplay = "negative";
+const nf = new Intl.NumberFormat("ko-KR", {signDisplay: "negative"});
+
+verifyFormatParts(nf.formatToParts(-Infinity), [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}], `-Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-987), [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}], `-987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0.0001), [{"type":"integer","value":"0"}], `-0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0), [{"type":"integer","value":"0"}], `-0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0), [{"type":"integer","value":"0"}], `0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0.0001), [{"type":"integer","value":"0"}], `0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(987), [{"type":"integer","value":"987"}], `987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(Infinity), [{"type":"infinity","value":"∞"}], `Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(NaN), [{"type":"nan","value":"NaN"}], `NaN (${signDisplay})`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-zh-TW.js
new file mode 100644
index 0000000000..902fa281c1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-negative-zh-TW.js
@@ -0,0 +1,35 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-v3]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const signDisplay = "negative";
+const nf = new Intl.NumberFormat("zh-TW", {signDisplay: "negative"});
+
+verifyFormatParts(nf.formatToParts(-Infinity), [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}], `-Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-987), [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}], `-987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0.0001), [{"type":"integer","value":"0"}], `-0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(-0), [{"type":"integer","value":"0"}], `-0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0), [{"type":"integer","value":"0"}], `0 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(0.0001), [{"type":"integer","value":"0"}], `0.0001 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(987), [{"type":"integer","value":"987"}], `987 (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(Infinity), [{"type":"infinity","value":"∞"}], `Infinity (${signDisplay})`);
+verifyFormatParts(nf.formatToParts(NaN), [{"type":"nan","value":"非數值"}], `NaN (${signDisplay})`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-zh-TW.js
new file mode 100644
index 0000000000..e3d2d30fa1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/signDisplay-zh-TW.js
@@ -0,0 +1,87 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the signDisplay option to the NumberFormat constructor.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ "auto",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"非數值"}],
+ ],
+ [
+ "always",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"plusSign","value":"+"},{"type":"nan","value":"非數值"}],
+ ],
+ [
+ "never",
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"987"}],
+ [{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"非數值"}],
+ ],
+ [
+ "exceptZero",
+ [{"type":"minusSign","value":"-"},{"type":"infinity","value":"∞"}],
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"integer","value":"0"}],
+ [{"type":"plusSign","value":"+"},{"type":"integer","value":"987"}],
+ [{"type":"plusSign","value":"+"},{"type":"infinity","value":"∞"}],
+ [{"type":"nan","value":"非數值"}],
+ ],
+];
+
+for (const [signDisplay, ...expected] of tests) {
+ const nf = new Intl.NumberFormat("zh-TW", {signDisplay});
+ verifyFormatParts(nf.formatToParts(-Infinity), expected[0], `-Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-987), expected[1], `-987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0.0001), expected[2], `-0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(-0), expected[3], `-0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0), expected[4], `0 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(0.0001), expected[5], `0.0001 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(987), expected[6], `987 (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(Infinity), expected[7], `Infinity (${signDisplay})`);
+ verifyFormatParts(nf.formatToParts(NaN), expected[8], `NaN (${signDisplay})`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/this-value-not-numberformat.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/this-value-not-numberformat.js
new file mode 100644
index 0000000000..e5beb30623
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/this-value-not-numberformat.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.formatToParts
+description: >
+ Tests that Intl.NumberFormat.prototype.formatToParts throws a
+ TypeError if called on a non-object value or an object that hasn't
+ been initialized as a NumberFormat.
+---*/
+
+const invalidTargets = [undefined, null, true, 0, 'NumberFormat', [], {}, Symbol()];
+const fn = Intl.NumberFormat.prototype.formatToParts;
+
+invalidTargets.forEach(target => {
+ assert.throws(
+ TypeError,
+ () => fn.call(target),
+ `Calling formatToParts on ${String(target)} should throw a TypeError.`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-de-DE.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-de-DE.js
new file mode 100644
index 0000000000..ce186e9ecf
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-de-DE.js
@@ -0,0 +1,99 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+locale: [de-DE]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ -987,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+ [
+ -0,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+ [
+ 0,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":","},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+ [
+ 987,
+ {
+ "short":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"Kilometer pro Stunde"}],
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("de-DE", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ verifyFormatParts(nf.formatToParts(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-en-US.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-en-US.js
new file mode 100644
index 0000000000..db2220de22
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-en-US.js
@@ -0,0 +1,99 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+locale: [en-US]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ -987,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+ [
+ -0,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+ [
+ 0,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+ [
+ 987,
+ {
+ "short":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"kilometers per hour"}],
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("en-US", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ verifyFormatParts(nf.formatToParts(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js
new file mode 100644
index 0000000000..2296f529a8
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js
@@ -0,0 +1,99 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+locale: [ja-JP]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ -987,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+ [
+ -0,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+ [
+ 0,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+ [
+ 987,
+ {
+ "short":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"時速"},{"type":"literal","value":" "},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"キロメートル"}],
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("ja-JP", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ verifyFormatParts(nf.formatToParts(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js
new file mode 100644
index 0000000000..74d938b513
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js
@@ -0,0 +1,99 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+locale: [ko-KR]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ -987,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+ [
+ -0,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+ [
+ 0,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+ [
+ 987,
+ {
+ "short":
+ [{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "narrow":
+ [{"type":"integer","value":"987"},{"type":"unit","value":"km/h"}],
+ "long":
+ [{"type":"unit","value":"시속"},{"type":"literal","value":" "},{"type":"integer","value":"987"},{"type":"unit","value":"킬로미터"}],
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("ko-KR", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ verifyFormatParts(nf.formatToParts(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js
new file mode 100644
index 0000000000..6399ff7023
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js
@@ -0,0 +1,99 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+locale: [zh-TW]
+features: [Intl.NumberFormat-unified]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(Array.isArray(expected), true, `${message}: expected is Array`);
+ assert.sameValue(Array.isArray(actual), true, `${message}: actual is Array`);
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ }
+}
+
+const tests = [
+ [
+ -987,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+ [
+ -0.001,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+ [
+ -0,
+ {
+ "short":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"minusSign","value":"-"},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+ [
+ 0,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+ [
+ 0.001,
+ {
+ "short":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"integer","value":"0"},{"type":"decimal","value":"."},{"type":"fraction","value":"001"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+ [
+ 987,
+ {
+ "short":
+ [{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"公里/小時"}],
+ "narrow":
+ [{"type":"integer","value":"987"},{"type":"unit","value":"公里/小時"}],
+ "long":
+ [{"type":"unit","value":"每小時"},{"type":"literal","value":" "},{"type":"integer","value":"987"},{"type":"literal","value":" "},{"type":"unit","value":"公里"}],
+ }
+ ],
+];
+
+for (const [number, expectedData] of tests) {
+ for (const [unitDisplay, expected] of Object.entries(expectedData)) {
+ const nf = new Intl.NumberFormat("zh-TW", { style: "unit", unit: "kilometer-per-hour", unitDisplay });
+ verifyFormatParts(nf.formatToParts(number), expected);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit.js
new file mode 100644
index 0000000000..54b165ed89
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/unit.js
@@ -0,0 +1,27 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: Checks handling of the unit style.
+features: [Intl.NumberFormat-unified]
+---*/
+
+const numbers = [-987, -0.001, -0, 0, 0.001, 987];
+const displays = [
+ "short",
+ "narrow",
+ "long",
+];
+
+for (const unitDisplay of displays) {
+ const nf = new Intl.NumberFormat("en-US", { style: "unit", unit: "meter", unitDisplay });
+ for (const number of numbers) {
+ const result = nf.formatToParts(number);
+ assert.sameValue(result.map(({ value }) => value).join(""), nf.format(number));
+ assert.sameValue(result.some(({ type }) => type === "unit"), true);
+ }
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/value-tonumber.js b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/value-tonumber.js
new file mode 100644
index 0000000000..c2601b026f
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/formatToParts/value-tonumber.js
@@ -0,0 +1,52 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.formattoparts
+description: >
+ Tests that Intl.NumberFormat.prototype.formatToParts converts
+ its argument (called value) to a number using ToNumber (7.1.3).
+info: |
+ 11.1.4 Number Format Functions
+
+ 4. Let x be ? ToNumber(value).
+features: [Symbol]
+---*/
+
+const toNumberResults = [
+ [undefined, NaN],
+ [null, +0],
+ [true, 1],
+ [false, +0],
+ ['42', 42],
+ ['foo', NaN]
+];
+
+const nf = new Intl.NumberFormat();
+
+function assertSameParts(actual, expected) {
+ assert.sameValue(actual.length, expected.length);
+ for (let i = 0; i < expected.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type);
+ assert.sameValue(actual[i].value, expected[i].value);
+ }
+}
+
+toNumberResults.forEach(pair => {
+ const [value, result] = pair;
+ assertSameParts(nf.formatToParts(value), nf.formatToParts(result));
+});
+
+let count = 0;
+const dummy = {};
+dummy[Symbol.toPrimitive] = hint => (hint === 'number' ? ++count : NaN);
+assertSameParts(nf.formatToParts(dummy), nf.formatToParts(count));
+assert.sameValue(count, 1);
+
+assert.throws(
+ TypeError,
+ () => nf.formatToParts(Symbol()),
+ "ToNumber(arg) throws a TypeError when arg is of type 'Symbol'"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/prop-desc.js
new file mode 100644
index 0000000000..24b65adc9a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/prop-desc.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.2.1
+description: Tests that Intl.NumberFormat.prototype has the required attributes.
+author: Norbert Lindenberg
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/basic.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/basic.js
new file mode 100644
index 0000000000..ffde22cf3a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/basic.js
@@ -0,0 +1,45 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2022 Apple Inc. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.3.3
+description: >
+ Tests that the object returned by
+ Intl.NumberFormat.prototype.resolvedOptions has the right
+ properties.
+author: Norbert Lindenberg
+includes: [testIntl.js, propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+var actual = new Intl.NumberFormat().resolvedOptions();
+
+var actual2 = new Intl.NumberFormat().resolvedOptions();
+assert.notSameValue(actual2, actual, "resolvedOptions returned the same object twice.");
+
+// this assumes the default values where the specification provides them
+assert(isCanonicalizedStructurallyValidLanguageTag(actual.locale),
+ "Invalid locale: " + actual.locale);
+assert(isValidNumberingSystem(actual.numberingSystem),
+ "Invalid numbering system: " + actual.numberingSystem);
+assert.sameValue(actual.style, "decimal");
+assert.sameValue(actual.minimumIntegerDigits, 1);
+assert.sameValue(actual.minimumFractionDigits, 0);
+assert.sameValue(actual.maximumFractionDigits, 3);
+assert.sameValue(actual.useGrouping, "auto");
+
+var dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+verifyProperty(actual, "locale", dataPropertyDesc);
+verifyProperty(actual, "numberingSystem", dataPropertyDesc);
+verifyProperty(actual, "style", dataPropertyDesc);
+verifyProperty(actual, "currency", undefined);
+verifyProperty(actual, "currencyDisplay", undefined);
+verifyProperty(actual, "minimumIntegerDigits", dataPropertyDesc);
+verifyProperty(actual, "minimumFractionDigits", dataPropertyDesc);
+verifyProperty(actual, "maximumFractionDigits", dataPropertyDesc);
+verifyProperty(actual, "minimumSignificantDigits", undefined);
+verifyProperty(actual, "maximumSignificantDigits", undefined);
+verifyProperty(actual, "useGrouping", dataPropertyDesc);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/builtin.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/builtin.js
new file mode 100644
index 0000000000..c513cc7fa5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.3.3_L15
+description: >
+ Tests that Intl.NumberFormat.prototype.resolvedOptions meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.NumberFormat.prototype.resolvedOptions), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.NumberFormat.prototype.resolvedOptions),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.NumberFormat.prototype.resolvedOptions), Function.prototype);
+
+assert.sameValue(Intl.NumberFormat.prototype.resolvedOptions.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.NumberFormat.prototype.resolvedOptions), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/compactDisplay.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/compactDisplay.js
new file mode 100644
index 0000000000..9d42cee786
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/compactDisplay.js
@@ -0,0 +1,26 @@
+// Copyright 2019 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: Verifies the existence of the compactDisplay property for the object returned by resolvedOptions().
+features: [Intl.NumberFormat-unified]
+---*/
+
+for (const notation of [undefined, "standard", "scientific", "engineering"]) {
+ const options = new Intl.NumberFormat([], {
+ notation,
+ compactDisplay: "long",
+ }).resolvedOptions();
+ assert.sameValue("compactDisplay" in options, false, `There should be no compactDisplay property with notation=${notation}`);
+ assert.sameValue(options.compactDisplay, undefined, `The compactDisplay property should be undefined with notation=${notation}`);
+}
+
+const options = new Intl.NumberFormat([], {
+ notation: "compact",
+ compactDisplay: "long",
+}).resolvedOptions();
+assert.sameValue("compactDisplay" in options, true, "There should be a compactDisplay property with notation=compact");
+assert.sameValue(options.compactDisplay, "long", "The compactDisplay property should be defined with notation=compact");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..44fb64a705
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: >
+ Intl.NumberFormat.prototype.resolvedOptions.length is 0.
+info: |
+ Intl.NumberFormat.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..584e60f57d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.resolvedOptions
+description: >
+ Intl.NumberFormat.prototype.resolvedOptions.name is "resolvedOptions".
+info: |
+ 11.4.4 Intl.NumberFormat.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/no-instanceof.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/no-instanceof.js
new file mode 100644
index 0000000000..e1be473f7d
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/no-instanceof.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: >
+ Tests that Intl.NumberFormat.prototype.resolvedOptions calls
+ OrdinaryHasInstance instead of the instanceof operator which includes a
+ Symbol.hasInstance lookup and call among other things.
+info: >
+ UnwrapNumberFormat ( nf )
+
+ 2. If nf does not have an [[InitializedNumberFormat]] internal slot and
+ ? OrdinaryHasInstance(%NumberFormat%, nf) is true, then
+ a. Return ? Get(nf, %Intl%.[[FallbackSymbol]]).
+---*/
+
+const nf = Object.create(Intl.NumberFormat.prototype);
+
+Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+ get() { throw new Test262Error(); }
+});
+
+assert.throws(TypeError, () => nf.resolvedOptions());
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..16f35fb743
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/order.js
@@ -0,0 +1,43 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.NumberFormat-unified]
+---*/
+
+const options = new Intl.NumberFormat([], {
+ "style": "currency",
+ "currency": "EUR",
+ "currencyDisplay": "symbol",
+ "minimumSignificantDigits": 1,
+ "maximumSignificantDigits": 2,
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "numberingSystem",
+ "style",
+ "currency",
+ "currencyDisplay",
+ "currencySign",
+ "minimumIntegerDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+ "useGrouping",
+ "notation",
+ "signDisplay",
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..d871d8b225
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: >
+ "resolvedOptions" property of Intl.NumberFormat.prototype.
+info: |
+ Intl.NumberFormat.prototype.resolvedOptions ()
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/return-keys-order-default.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/return-keys-order-default.js
new file mode 100644
index 0000000000..15ad912032
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/return-keys-order-default.js
@@ -0,0 +1,49 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: order of property keys for the object returned by resolvedOptions()
+features: [Intl.NumberFormat-v3]
+includes: [compareArray.js]
+---*/
+
+const allKeys = [
+ 'locale',
+ 'numberingSystem',
+ 'style',
+ 'currency',
+ 'currencyDisplay',
+ 'currencySign',
+ 'unit',
+ 'unitDisplay',
+ 'minimumIntegerDigits',
+ 'minimumFractionDigits',
+ 'maximumFractionDigits',
+ 'minimumSignificantDigits',
+ 'maximumSignificantDigits',
+ 'useGrouping',
+ 'notation',
+ 'compactDisplay',
+ 'signDisplay',
+ 'roundingIncrement',
+ 'roundingMode',
+ 'roundingPriority',
+ 'trailingZeroDisplay'
+];
+
+const optionsBase = { notation: 'compact' };
+const optionsExtensions = [
+ { style: 'currency', currency: 'XTS' },
+ { style: 'unit', unit: 'percent' },
+];
+optionsExtensions.forEach((optionsExtension) => {
+ const options = Object.assign({}, optionsBase, optionsExtension);
+ const nf = new Intl.NumberFormat(undefined, options);
+ const resolved = nf.resolvedOptions();
+ const resolvedKeys = Reflect.ownKeys(resolved);
+ const expectedKeys = allKeys.filter(key => key in resolved);
+ assert.compareArray(resolvedKeys, expectedKeys,
+ 'resolvedOptions() property key order with options ' + JSON.stringify(options));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/roundingMode.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/roundingMode.js
new file mode 100644
index 0000000000..10ab976f1c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/roundingMode.js
@@ -0,0 +1,42 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// Copyright 2021 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: roundingMode property for the object returned by resolvedOptions()
+features: [Intl.NumberFormat-v3]
+---*/
+
+var options;
+
+options = new Intl.NumberFormat([], {}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfExpand', 'default');
+
+options = new Intl.NumberFormat([], {roundingMode: 'ceil'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'ceil');
+
+options = new Intl.NumberFormat([], {roundingMode: 'floor'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'floor');
+
+options = new Intl.NumberFormat([], {roundingMode: 'expand'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'expand');
+
+options = new Intl.NumberFormat([], {roundingMode: 'trunc'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'trunc');
+
+options = new Intl.NumberFormat([], {roundingMode: 'halfCeil'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfCeil');
+
+options = new Intl.NumberFormat([], {roundingMode: 'halfFloor'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfFloor');
+
+options = new Intl.NumberFormat([], {roundingMode: 'halfExpand'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfExpand');
+
+options = new Intl.NumberFormat([], {roundingMode: 'halfTrunc'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfTrunc');
+
+options = new Intl.NumberFormat([], {roundingMode: 'halfEven'}).resolvedOptions();
+assert.sameValue(options.roundingMode, 'halfEven');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/this-value-not-numberformat.js b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/this-value-not-numberformat.js
new file mode 100644
index 0000000000..d9773f00a3
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/resolvedOptions/this-value-not-numberformat.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.prototype.resolvedOptions
+description: >
+ Tests that Intl.NumberFormat.prototype.resolvedOptions throws a
+ TypeError if called on a non-object value or an object that hasn't
+ been initialized as a NumberFormat.
+---*/
+
+const invalidTargets = [undefined, null, true, 0, 'NumberFormat', [], {}, Symbol()];
+const fn = Intl.NumberFormat.prototype.resolvedOptions;
+
+invalidTargets.forEach(target => {
+ assert.throws(
+ TypeError,
+ () => fn.call(target),
+ `Calling resolvedOptions on ${String(target)} should throw a TypeError.`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/this-value-numberformat-prototype.js b/js/src/tests/test262/intl402/NumberFormat/prototype/this-value-numberformat-prototype.js
new file mode 100644
index 0000000000..8fed1e229a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/this-value-numberformat-prototype.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-numberformat-prototype-object
+description: >
+ Tests that Intl.NumberFormat.prototype is not an object that has been
+ initialized as an Intl.NumberFormat.
+author: Roozbeh Pournader
+---*/
+
+// test by calling a function that should fail as "this" is not an object
+// initialized as an Intl.NumberFormat
+assert.throws(TypeError, () => Intl.NumberFormat.prototype.format(0),
+ "Intl.NumberFormat's prototype is not an object that has been initialized as an Intl.NumberFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/configurable.js b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/configurable.js
new file mode 100644
index 0000000000..a6aa44d04a
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/configurable.js
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype-@@tostringtag
+description: >
+ Check that the initial value of the property is "Intl.NumberFormat" and that any changes
+ made by reconfiguring are reflected.
+---*/
+
+assert.sameValue(Intl.NumberFormat.prototype[Symbol.toStringTag], 'Intl.NumberFormat');
+assert.sameValue(
+ Object.prototype.toString.call(new Intl.NumberFormat()),
+ '[object Intl.NumberFormat]'
+);
+
+Object.defineProperty(Intl.NumberFormat.prototype, Symbol.toStringTag, {
+ value: 'Alpha'
+});
+
+assert.sameValue(Intl.NumberFormat.prototype[Symbol.toStringTag], 'Alpha');
+assert.sameValue(
+ Object.prototype.toString.call(new Intl.NumberFormat()),
+ '[object Alpha]'
+);
+
+delete Intl.NumberFormat.prototype[Symbol.toStringTag];
+
+assert.sameValue(Intl.NumberFormat.prototype[Symbol.toStringTag], undefined);
+assert.sameValue(
+ Object.prototype.toString.call(new Intl.NumberFormat()),
+ '[object Object]'
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/prop-desc.js
new file mode 100644
index 0000000000..e5116e6dec
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/prop-desc.js
@@ -0,0 +1,18 @@
+// Copyright (C) 2018 Ujjwal Sharma. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype-@@tostringtag
+description: >
+ Tests that Intl.NumberFormat.prototype[@@toStringTag] has the required attributes.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.prototype, Symbol.toStringTag, {
+ value: 'Intl.NumberFormat',
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/shell.js b/js/src/tests/test262/intl402/NumberFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/shell.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/significant-digits-options-get-sequence.js b/js/src/tests/test262/intl402/NumberFormat/significant-digits-options-get-sequence.js
new file mode 100644
index 0000000000..0602e55b9c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/significant-digits-options-get-sequence.js
@@ -0,0 +1,42 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_32
+description: >
+ Tests that the options minimumSignificantDigits and
+ maximumSignificantDigits are read in the right sequence.
+author: Norbert Lindenberg
+---*/
+
+var minimumSignificantDigitsRead = false;
+var maximumSignificantDigitsRead = false;
+
+function readMinimumSignificantDigits() {
+ assert.sameValue(minimumSignificantDigitsRead, false,
+ "minimumSignificantDigits getter already called");
+ assert.sameValue(maximumSignificantDigitsRead, false,
+ "maximumSignificantDigits getter called before minimumSignificantDigits");
+ minimumSignificantDigitsRead = true;
+ return 1;
+}
+
+function readMaximumSignificantDigits() {
+ assert.sameValue(maximumSignificantDigitsRead, false,
+ "maximumSignificantDigits getter already called");
+ maximumSignificantDigitsRead = true;
+ return 1;
+}
+
+var options = {};
+Object.defineProperty(options, "minimumSignificantDigits",
+ { get: readMinimumSignificantDigits });
+Object.defineProperty(options, "maximumSignificantDigits",
+ { get: readMaximumSignificantDigits });
+
+new Intl.NumberFormat("de", options);
+
+assert(minimumSignificantDigitsRead, "minimumSignificantDigits getter was called once");
+assert(maximumSignificantDigitsRead, "maximumSignificantDigits getter was called once");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/style-unit.js b/js/src/tests/test262/intl402/NumberFormat/style-unit.js
new file mode 100644
index 0000000000..f41406dcfe
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/style-unit.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-setnumberformatunitoptions
+description: Checks handling of valid values for the numeric option to the RelativeTimeFormat constructor.
+info: |
+ SetNumberFormatUnitOptions ( intlObj, options )
+
+ 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal").
+ 4. Set intlObj.[[Style]] to style.
+
+features: [Intl.NumberFormat-unified]
+---*/
+
+const validOptions = [
+ [undefined, "decimal"],
+ ["unit", "unit"],
+ [{ toString() { return "unit"; } }, "unit"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const nf = new Intl.NumberFormat([], {"style": validOption, "unit": "gigabit"});
+ const resolvedOptions = nf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/subclassing.js b/js/src/tests/test262/intl402/NumberFormat/subclassing.js
new file mode 100644
index 0000000000..e143233d50
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/subclassing.js
@@ -0,0 +1,30 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.2
+description: Tests that Intl.NumberFormat can be subclassed.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+// get a number format and have it format an array of numbers for comparison with the subclass
+var locales = ["tlh", "id", "en"];
+var a = [0, 1, -1, -123456.789, -Infinity, NaN];
+var referenceNumberFormat = new Intl.NumberFormat(locales);
+var referenceFormatted = a.map(referenceNumberFormat.format);
+
+class MyNumberFormat extends Intl.NumberFormat {
+ constructor(locales, options) {
+ super(locales, options);
+ // could initialize MyNumberFormat properties
+ }
+ // could add methods to MyNumberFormat.prototype
+}
+
+var format = new MyNumberFormat(locales);
+var actual = a.map(format.format);
+assert.compareArray(actual, referenceFormatted);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..fbf53ad990
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/basic.js
@@ -0,0 +1,25 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.2.2_a
+description: >
+ Tests that Intl.NumberFormat has a supportedLocalesOf property,
+ and it works as planned.
+author: Roozbeh Pournader
+---*/
+
+var defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
+var notSupported = 'zxx'; // "no linguistic content"
+var requestedLocales = [defaultLocale, notSupported];
+
+var supportedLocales;
+
+assert(Intl.NumberFormat.hasOwnProperty('supportedLocalesOf'), "Intl.NumberFormat doesn't have a supportedLocalesOf property.");
+
+supportedLocales = Intl.NumberFormat.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/builtin.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/builtin.js
new file mode 100644
index 0000000000..0c5efac8c5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.2.2_L15
+description: >
+ Tests that Intl.NumberFormat.supportedLocalesOf meets the
+ requirements for built-in objects defined by the introduction of
+ chapter 17 of the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.NumberFormat.supportedLocalesOf), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.NumberFormat.supportedLocalesOf),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.NumberFormat.supportedLocalesOf), Function.prototype);
+
+assert.sameValue(Intl.NumberFormat.supportedLocalesOf.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.NumberFormat.supportedLocalesOf), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..e18f05e14b
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.supportedlocalesof
+description: >
+ Intl.NumberFormat.supportedLocalesOf.length is 1.
+info: |
+ Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..8abc567de5
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/name.js
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.NumberFormat.supportedLocalesOf
+description: >
+ Intl.NumberFormat.supportedLocalesOf.name is "supportedLocalesOf".
+info: |
+ 11.3.2 Intl.NumberFormat.supportedLocalesOf (locales [ , options ])
+
+ 17 ECMAScript Standard Built-in Objects:
+ Every built-in Function object, including constructors, that is not
+ identified as an anonymous function has a name property whose value
+ is a String.
+
+ Unless otherwise specified, the name property of a built-in Function
+ object, if it exists, has the attributes { [[Writable]]: false,
+ [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..d6476db47e
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.supportedlocalesof
+description: >
+ "supportedLocalesOf" property of Intl.NumberFormat.
+info: |
+ Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] )
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.NumberFormat, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/taint-Object-prototype.js b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/taint-Object-prototype.js
new file mode 100644
index 0000000000..d8644585e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/supportedLocalesOf/taint-Object-prototype.js
@@ -0,0 +1,16 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 11.2.2_b
+description: >
+ Tests that Intl.NumberFormat.supportedLocalesOf doesn't access
+ arguments that it's not given.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Object.prototype, "1");
+new Intl.NumberFormat("und");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/taint-Object-prototype.js b/js/src/tests/test262/intl402/NumberFormat/taint-Object-prototype.js
new file mode 100644
index 0000000000..19a41731d8
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/taint-Object-prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_6
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["localeMatcher"]);
+
+var locale = new Intl.NumberFormat(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+assert(isCanonicalizedStructurallyValidLanguageTag(locale), "NumberFormat returns invalid locale " + locale + ".");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-currency.js b/js/src/tests/test262/intl402/NumberFormat/test-option-currency.js
new file mode 100644
index 0000000000..d1d786fab1
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-currency.js
@@ -0,0 +1,57 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_17
+description: Tests that the option currency is processed correctly.
+author: Norbert Lindenberg
+---*/
+
+var validValues = ["CNY", "USD", "EUR", "IDR", "jpy", {toString: function () {return "INR";}}];
+var invalidValues = ["$", "SFr.", "US$", "ßP", {toString: function () {return;}}];
+
+var defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
+
+validValues.forEach(function (value) {
+ var format, actual, expected;
+
+ // with currency style, we should get the upper case form back
+ format = new Intl.NumberFormat([defaultLocale], {style: "currency", currency: value});
+ actual = format.resolvedOptions().currency;
+ expected = value.toString().toUpperCase();
+ assert.sameValue(actual, expected, "Incorrect resolved currency with currency style.");
+
+ // without currency style, we shouldn't get any currency back
+ format = new Intl.NumberFormat([defaultLocale], {currency: value});
+ actual = format.resolvedOptions().currency;
+ expected = undefined;
+ assert.sameValue(actual, expected, "Incorrect resolved currency with non-currency style.");
+
+ // currencies specified through the locale must be ignored
+ format = new Intl.NumberFormat([defaultLocale + "-u-cu-krw"], {style: "currency", currency: value});
+ actual = format.resolvedOptions().currency;
+ expected = value.toString().toUpperCase();
+ assert.sameValue(actual, expected, "Incorrect resolved currency with -u-cu- and currency style.");
+
+ format = new Intl.NumberFormat([defaultLocale + "-u-cu-krw"], {currency: value});
+ actual = format.resolvedOptions().currency;
+ expected = undefined;
+ assert.sameValue(actual, expected, "Incorrect resolved currency with -u-cu- and non-currency style.");
+});
+
+invalidValues.forEach(function (value) {
+ assert.throws(RangeError, function () {
+ return new Intl.NumberFormat([defaultLocale], {style: "currency", currency: value});
+ }, "Invalid currency value " + value + " was not rejected.");
+ assert.throws(RangeError, function () {
+ return new Intl.NumberFormat([defaultLocale], {currency: value});
+ }, "Invalid currency value " + value + " was not rejected.");
+ assert.throws(RangeError, function () {
+ return new Intl.NumberFormat([defaultLocale + "-u-cu-krw"], {style: "currency", currency: value});
+ }, "Invalid currency value " + value + " was not rejected.");
+ assert.throws(RangeError, function () {
+ return new Intl.NumberFormat([defaultLocale + "-u-cu-krw"], {currency: value});
+ }, "Invalid currency value " + value + " was not rejected.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-currencyDisplay.js b/js/src/tests/test262/intl402/NumberFormat/test-option-currencyDisplay.js
new file mode 100644
index 0000000000..afc92c1151
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-currencyDisplay.js
@@ -0,0 +1,16 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_21
+description: Tests that the option currencyDisplay is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.NumberFormat, "currencyDisplay", "string", ["code", "symbol", "name"],
+ "symbol", {extra: {any: {style: "currency", currency: "XDR"}}});
+testOption(Intl.NumberFormat, "currencyDisplay", "string", ["code", "symbol", "name"],
+ undefined, {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-localeMatcher.js b/js/src/tests/test262/intl402/NumberFormat/test-option-localeMatcher.js
new file mode 100644
index 0000000000..b2525bc392
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-localeMatcher.js
@@ -0,0 +1,13 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_7
+description: Tests that the option localeMatcher is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.NumberFormat, "localeMatcher", "string", ["lookup", "best fit"], "best fit", {noReturn: true});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority-mixed-options.js b/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority-mixed-options.js
new file mode 100644
index 0000000000..211be9d4c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority-mixed-options.js
@@ -0,0 +1,96 @@
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.format
+description: Tests that the digits are determined correctly when specifying at same time «"minimumFractionDigits", "maximumFractionDigits", "minimumSignificantDigits", "maximumSignificantDigits"»
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+const locales = [new Intl.NumberFormat().resolvedOptions().locale, 'ar', 'de', 'th', 'ja'];
+
+const numberingSystems = ['arab', 'latn', 'thai', 'hanidec'];
+
+const expectedResults = {
+ 'morePrecision': {
+ 'same-minimums': { '1': '1.0', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.0' },
+ 'same-maximums': { '1': '1', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2' },
+ 'minSd-larger-minFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'minSd-smaller-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'minSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'minSd-larger-maxFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'maxSd-larger-minFd': { '1': '1.0', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.0' },
+ 'maxSd-smaller-minFd': { '1': '1.000', '1.500': '1.500', '1.625': '1.625', '1.750': '1.750', '1.875': '1.875', '2.000': '2.000' },
+ 'maxSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'maxSd-larger-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2' },
+ 'minSd-maxSd-smaller-minFd-maxFd': { '1': '1.000', '1.500': '1.500', '1.625': '1.625', '1.750': '1.750', '1.875': '1.875', '2.000': '2.000' },
+ 'minSd-maxSd-larger-minFd-maxFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'same-minimums-maxFd-larger-maxSd': { '1': '1.0', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.0' },
+ 'same-minimums-maxFd-smaller-maxSd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ },
+ 'lessPrecision': {
+ 'same-minimums': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'same-maximums': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'minSd-larger-minFd': { '1': '1.0', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.0' },
+ 'minSd-smaller-minFd': { '1': '1.000', '1.500': '1.500', '1.625': '1.625', '1.750': '1.750', '1.875': '1.875', '2.000': '2.000' },
+ 'minSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'minSd-larger-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'maxSd-larger-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2' },
+ 'maxSd-smaller-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'maxSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'maxSd-larger-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'minSd-maxSd-smaller-minFd-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'minSd-maxSd-larger-minFd-maxFd': { '1': '1.0', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2.0' },
+ 'same-minimums-maxFd-larger-maxSd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'same-minimums-maxFd-smaller-maxSd': { '1': '1.0', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2.0' },
+ },
+ 'auto': {
+ 'same-minimums': { '1': '1.0', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.0' },
+ 'same-maximums': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'minSd-larger-minFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'minSd-smaller-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'minSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ 'minSd-larger-maxFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'maxSd-larger-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2' },
+ 'maxSd-smaller-minFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'maxSd-smaller-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'maxSd-larger-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.63', '1.750': '1.75', '1.875': '1.88', '2.000': '2' },
+ 'minSd-maxSd-smaller-minFd-maxFd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'minSd-maxSd-larger-minFd-maxFd': { '1': '1.00', '1.500': '1.50', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2.00' },
+ 'same-minimums-maxFd-larger-maxSd': { '1': '1', '1.500': '1.5', '1.625': '1.6', '1.750': '1.8', '1.875': '1.9', '2.000': '2' },
+ 'same-minimums-maxFd-smaller-maxSd': { '1': '1', '1.500': '1.5', '1.625': '1.625', '1.750': '1.75', '1.875': '1.875', '2.000': '2' },
+ },
+};
+
+const optionsMatrix = {
+ 'same-minimums': { minimumSignificantDigits: 2, minimumFractionDigits: 2 },
+ 'same-maximums': { maximumSignificantDigits: 2, maximumFractionDigits: 2 },
+ 'minSd-larger-minFd': { minimumSignificantDigits: 3, minimumFractionDigits: 1 },
+ 'minSd-smaller-minFd': { minimumSignificantDigits: 1, minimumFractionDigits: 3 },
+ 'minSd-smaller-maxFd': { minimumSignificantDigits: 1, maximumFractionDigits: 3 },
+ 'minSd-larger-maxFd': { minimumSignificantDigits: 3, maximumFractionDigits: 1 },
+ 'maxSd-larger-minFd': { maximumSignificantDigits: 3, minimumFractionDigits: 1 },
+ 'maxSd-smaller-minFd': { maximumSignificantDigits: 2, minimumFractionDigits: 3 },
+ 'maxSd-smaller-maxFd': { maximumSignificantDigits: 2, maximumFractionDigits: 3 },
+ 'maxSd-larger-maxFd': { maximumSignificantDigits: 3, maximumFractionDigits: 1 },
+ 'minSd-maxSd-smaller-minFd-maxFd': { minimumSignificantDigits: 1, maximumSignificantDigits: 2, minimumFractionDigits: 3, maximumFractionDigits: 4 },
+ 'minSd-maxSd-larger-minFd-maxFd': { minimumSignificantDigits: 3, maximumSignificantDigits: 4, minimumFractionDigits: 1, maximumFractionDigits: 2 },
+ 'same-minimums-maxFd-larger-maxSd': { minimumSignificantDigits: 1, maximumSignificantDigits: 2, minimumFractionDigits: 1, maximumFractionDigits: 4 },
+ 'same-minimums-maxFd-smaller-maxSd': { minimumSignificantDigits: 1, maximumSignificantDigits: 4, minimumFractionDigits: 1, maximumFractionDigits: 2 },
+};
+
+function testPrecision(mode) {
+ Object.keys(optionsMatrix).forEach((key) => {
+ testNumberFormat(
+ locales,
+ numberingSystems,
+ { ...optionsMatrix[key], roundingPriority: mode, userGrouping: false },
+ expectedResults[mode][key]
+ );
+ });
+}
+
+['morePrecision', 'lessPrecision', 'auto'].forEach(testPrecision);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority.js b/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority.js
new file mode 100644
index 0000000000..afe01335fb
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-roundingPriority.js
@@ -0,0 +1,18 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.numberformat
+description: Tests that the option roundingPriority is processed correctly.
+features: [Intl.NumberFormat-v3]
+includes: [testIntl.js]
+---*/
+
+testOption(
+ Intl.NumberFormat,
+ "roundingPriority",
+ "string",
+ ["auto", "morePrecision", "lessPrecision"],
+ "auto"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-style.js b/js/src/tests/test262/intl402/NumberFormat/test-option-style.js
new file mode 100644
index 0000000000..fd9063b490
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-style.js
@@ -0,0 +1,14 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_15
+description: Tests that the option style is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testOption(Intl.NumberFormat, "style", "string", ["decimal", "percent", "currency"], "decimal",
+ {extra: {"currency": {currency: "CNY"}}});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping-extended.js b/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping-extended.js
new file mode 100644
index 0000000000..abead17ffd
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping-extended.js
@@ -0,0 +1,41 @@
+// Copyright 2021 the V8 project authors. All rights reserved.
+// Copyright 2022 Apple Inc. All rights reserved.
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: Tests that the option useGrouping is processed correctly.
+info: |
+ The "Intl.NumberFormat v3" proposal contradicts the behavior required by the
+ latest revision of ECMA402.
+features: [Intl.NumberFormat-v3]
+---*/
+
+function render(options) {
+ var nf = new Intl.NumberFormat(undefined, options);
+ return nf.resolvedOptions().useGrouping;
+}
+
+assert.sameValue(render({}), 'auto', '(omitted)');
+assert.sameValue(render({useGrouping: undefined}), 'auto', 'undefined');
+assert.sameValue(render({useGrouping: 'auto'}), 'auto', '"auto"');
+assert.sameValue(render({useGrouping: true}), 'always', 'true');
+assert.sameValue(render({useGrouping: 'always'}), 'always', '"always"');
+assert.sameValue(render({useGrouping: false}), false, 'false');
+assert.sameValue(render({useGrouping: null}), false, 'null');
+assert.sameValue(render({useGrouping: 'min2'}), 'min2', '"min2"');
+
+assert.sameValue(render({notation: 'compact'}), 'min2', 'compact, (omitted)');
+assert.sameValue(render({notation: 'compact', useGrouping: undefined}), 'min2', 'compact, undefined');
+assert.sameValue(render({notation: 'compact', useGrouping: 'auto'}), 'auto', 'compact, "auto"');
+assert.sameValue(render({notation: 'compact', useGrouping: true}), 'always', 'compact, true');
+assert.sameValue(render({notation: 'compact', useGrouping: 'always'}), 'always', 'compact, "always"');
+assert.sameValue(render({notation: 'compact', useGrouping: false}), false, 'compact, false');
+assert.sameValue(render({notation: 'compact', useGrouping: null}), false, 'compact, null');
+assert.sameValue(render({notation: 'compact', useGrouping: 'min2'}), 'min2', 'compact, "min2"');
+
+assert.sameValue(render({useGrouping: 'false'}), 'auto', 'use fallback value');
+assert.sameValue(render({useGrouping: 'true'}), 'auto', 'use fallback value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping.js b/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping.js
new file mode 100644
index 0000000000..13e1df9ab9
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/test-option-useGrouping.js
@@ -0,0 +1,42 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// Copyright 2022 Apple Inc. All rights reserved.
+// Copyright 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_34
+description: Tests that the option useGrouping is processed correctly.
+info: |
+ The "Intl.NumberFormat v3" proposal contradicts the behavior required by the
+ latest revision of ECMA402.
+author: Norbert Lindenberg
+features: [Intl.NumberFormat-v3]
+---*/
+
+function resolveUseGrouping(option) {
+ return new Intl.NumberFormat(undefined, { useGrouping: option }).resolvedOptions().useGrouping;
+}
+
+for (let string of ["min2", "auto", "always"]) {
+ assert.sameValue(resolveUseGrouping(string), string);
+}
+
+assert.sameValue(resolveUseGrouping(true), "always");
+assert.sameValue(resolveUseGrouping(false), false);
+assert.sameValue(resolveUseGrouping(undefined), "auto");
+assert.sameValue(resolveUseGrouping("true"), "auto");
+assert.sameValue(resolveUseGrouping("false"), "auto");
+
+for (let falsy of [0, null, ""]) {
+ assert.sameValue(resolveUseGrouping(falsy), false);
+}
+
+for (let invalidOptions of [42, "MIN2", {} , "True", "TRUE" , "FALSE" , "False" , "Undefined" , "undefined"]) {
+ assert.throws(RangeError, function () {
+ return new Intl.NumberFormat(undefined, { useGrouping: invalidOptions });
+ }, "Throws RangeError when useGrouping value is not supported");
+}
+
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/this-value-ignored.js b/js/src/tests/test262/intl402/NumberFormat/this-value-ignored.js
new file mode 100644
index 0000000000..ac07b22269
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/this-value-ignored.js
@@ -0,0 +1,37 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl-numberformat-constructor
+description: >
+ Tests that the this-value is ignored in NumberFormat, if the this-value
+ isn't a NumberFormat instance.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ if (Constructor === Intl.NumberFormat)
+ return;
+
+ var obj, newObj;
+
+ // variant 1: use constructor in a "new" expression
+ obj = new Constructor();
+ newObj = Intl.NumberFormat.call(obj);
+ assert.notSameValue(obj, newObj, "NumberFormat object created with \"new\" was not ignored as this-value.");
+
+ // variant 2: use constructor as a function
+ if (Constructor !== Intl.Collator &&
+ Constructor !== Intl.NumberFormat &&
+ Constructor !== Intl.DateTimeFormat)
+ {
+ // Newer Intl constructors are not callable as a function.
+ return;
+ }
+ obj = Constructor();
+ newObj = Intl.NumberFormat.call(obj);
+ assert.notSameValue(obj, newObj, "NumberFormat object created with constructor as function was not ignored as this-value.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/throws-for-currency-style-without-currency-option.js b/js/src/tests/test262/intl402/NumberFormat/throws-for-currency-style-without-currency-option.js
new file mode 100644
index 0000000000..74d7d2934c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/throws-for-currency-style-without-currency-option.js
@@ -0,0 +1,22 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 11.1.1_19
+description: >
+ Tests that the currency style can not be used without a specified
+ currency.
+author: Norbert Lindenberg
+---*/
+
+var defaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
+
+assert.throws(TypeError, function () {
+ return new Intl.NumberFormat([defaultLocale], {style: "currency"});
+}, "Throws TypeError when currency code is not specified.");
+
+assert.throws(TypeError, function () {
+ return new Intl.NumberFormat([defaultLocale + "-u-cu-krw"], {style: "currency"});
+}, "Throws TypeError when currency code is not specified; Currenty code from Unicode locale extension sequence is ignored.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-over-limit.js b/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-over-limit.js
new file mode 100644
index 0000000000..0f2602bb3c
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-over-limit.js
@@ -0,0 +1,24 @@
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options maximumFractionDigits limit to the range 0 - 100.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 25.a.ii. Set mxfd to ? DefaultNumberOption(mxfd, 0, 100, undefined).
+
+ DefaultNumberOption ( value, minimum, maximum, fallback )
+
+ 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
+---*/
+
+let wontThrow = new Intl.NumberFormat(undefined, {maximumFractionDigits: 100});
+
+assert.throws(RangeError, function () {
+ return new Intl.NumberFormat(undefined, {maximumFractionDigits: 101});
+}, "Throws RangeError when maximumFractionDigits is more than 100.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-under-limit.js b/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-under-limit.js
new file mode 100644
index 0000000000..446af0aeff
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/throws-for-maximumFractionDigits-under-limit.js
@@ -0,0 +1,24 @@
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options maximumFractionDigits limit to the range 0 - 100.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 25.a.ii. Set mxfd to ? DefaultNumberOption(mxfd, 0, 100, undefined).
+
+ DefaultNumberOption ( value, minimum, maximum, fallback )
+
+ 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
+---*/
+
+let wontThrow = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0});
+
+assert.throws(RangeError, function () {
+ return new Intl.NumberFormat(undefined, {maximumFractionDigits: -1});
+}, "Throws RangeError when maximumFractionDigits is less than 0.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-over-limit.js b/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-over-limit.js
new file mode 100644
index 0000000000..699a67a1a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-over-limit.js
@@ -0,0 +1,24 @@
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options minimumFractionDigits limit to the range 0 - 100.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 25.a.ii. Set mxfd to ? DefaultNumberOption(mxfd, 0, 100, undefined).
+
+ DefaultNumberOption ( value, minimum, maximum, fallback )
+
+ 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
+---*/
+
+let wontThrow = new Intl.NumberFormat(undefined, {minimumFractionDigits: 100});
+
+assert.throws(RangeError, function () {
+ return new Intl.NumberFormat(undefined, {minimumFractionDigits: 101});
+}, "Throws RangeError when minimumFractionDigits is more than 100.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-under-limit.js b/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-under-limit.js
new file mode 100644
index 0000000000..d12e2502ed
--- /dev/null
+++ b/js/src/tests/test262/intl402/NumberFormat/throws-for-minimumFractionDigits-under-limit.js
@@ -0,0 +1,24 @@
+// Copyright 2023 Google Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializenumberformat
+description: >
+ Tests that the options minimumFractionDigits limit to the range 0 - 100.
+info: |
+ InitializeNumberFormat ( numberFormat, locales, options )
+
+ 25.a.ii. Set mxfd to ? DefaultNumberOption(mxfd, 0, 100, undefined).
+
+ DefaultNumberOption ( value, minimum, maximum, fallback )
+
+ 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
+---*/
+
+let wontThrow = new Intl.NumberFormat(undefined, {minimumFractionDigits: 0});
+
+assert.throws(RangeError, function () {
+ return new Intl.NumberFormat(undefined, {minimumFractionDigits: -1});
+}, "Throws RangeError when minimumFractionDigits is less than 0.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/browser.js b/js/src/tests/test262/intl402/PluralRules/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/builtin.js b/js/src/tests/test262/intl402/PluralRules/builtin.js
new file mode 100644
index 0000000000..7979c93243
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/builtin.js
@@ -0,0 +1,21 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules
+description: >
+ Tests that Intl.PluralRules meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Zibi Braniecki
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.PluralRules), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.PluralRules), Function.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/can-be-subclassed.js b/js/src/tests/test262/intl402/PluralRules/can-be-subclassed.js
new file mode 100644
index 0000000000..e09c8b5f51
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/can-be-subclassed.js
@@ -0,0 +1,30 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl-pluralrules-constructor
+description: Tests that Intl.PluralRules can be subclassed.
+author: Zibi Braniecki
+includes: [compareArray.js]
+---*/
+
+// get a plural-rules and have it format an array of dates for comparison with the subclass
+var locales = ["tlh", "id", "en"];
+var a = [1, 5, 12];
+
+var referencePluralRules = new Intl.PluralRules(locales);
+var referenceSelected = a.map(referencePluralRules.select.bind(referencePluralRules));
+
+class MyPluralRules extends Intl.PluralRules {
+ constructor(locales, options) {
+ super(locales, options);
+ // could initialize MyPluralRules properties
+ }
+ // could add methods to MyPluralRules.prototype
+}
+
+var pr = new MyPluralRules(locales);
+var actual = a.map(pr.select.bind(pr));
+assert.compareArray(actual, referenceSelected);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/constructor-option-read-order.js b/js/src/tests/test262/intl402/PluralRules/constructor-option-read-order.js
new file mode 100644
index 0000000000..0fc3569e60
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/constructor-option-read-order.js
@@ -0,0 +1,43 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializepluralrules
+description: Checks the order of option read.
+features: [Intl.NumberFormat-v3]
+includes: [compareArray.js]
+---*/
+
+let optionKeys = [
+ // Inside InitializePluralRules
+ "localeMatcher",
+ "type",
+ // Inside SetNumberFormatDigitOptions
+ "minimumIntegerDigits",
+ "minimumFractionDigits",
+ "maximumFractionDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+ "roundingIncrement",
+ "roundingMode",
+ "roundingPriority",
+ "trailingZeroDisplay",
+ // End of SetNumberFormatDigitOptions
+];
+
+// Use getters to track the order of reading known properties.
+// TODO: Should we use a Proxy to detect *unexpected* property reads?
+let reads = new Array();
+let options = {};
+optionKeys.forEach((key) => {
+ Object.defineProperty(options, key, {
+ get() {
+ reads.push(key);
+ return undefined;
+ },
+ });
+});
+new Intl.PluralRules(undefined, options);
+assert.compareArray(reads, optionKeys, "Intl.PluralRules options read order");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/constructor-options-throwing-getters.js b/js/src/tests/test262/intl402/PluralRules/constructor-options-throwing-getters.js
new file mode 100644
index 0000000000..3099b1d1a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/constructor-options-throwing-getters.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializepluralrules
+description: Checks the propagation of exceptions from the options for the NumberFormat constructor.
+---*/
+
+function CustomError() {}
+
+const options = [
+ "localeMatcher",
+ "type",
+ "minimumIntegerDigits",
+ "minimumFractionDigits",
+ "maximumFractionDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.PluralRules("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/default-options-object-prototype.js b/js/src/tests/test262/intl402/PluralRules/default-options-object-prototype.js
new file mode 100644
index 0000000000..fc837a2e2c
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/default-options-object-prototype.js
@@ -0,0 +1,20 @@
+// Copyright (C) 2017 Igalia, S. L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializepluralrules
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for PluralRules as a null prototype is used.
+info: |
+ InitializePluralRules ( collator, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+---*/
+
+Object.prototype.type = "ordinal";
+let pluralRules = new Intl.PluralRules("en");
+assert.sameValue(pluralRules.resolvedOptions().type, "cardinal");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/internals.js b/js/src/tests/test262/intl402/PluralRules/internals.js
new file mode 100644
index 0000000000..db0b3a19b9
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/internals.js
@@ -0,0 +1,19 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl-pluralrules-constructor
+description: >
+ Tests that objects constructed by Intl.PluralRules have the specified
+ internal properties.
+author: Zibi Braniecki
+---*/
+
+var obj = new Intl.PluralRules();
+
+var actualPrototype = Object.getPrototypeOf(obj);
+assert.sameValue(actualPrototype, Intl.PluralRules.prototype, "Prototype of object constructed by Intl.PluralRules isn't Intl.PluralRules.prototype; got " + actualPrototype);
+
+assert(Object.isExtensible(obj), "Object constructed by Intl.PluralRules must be extensible.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/length.js b/js/src/tests/test262/intl402/PluralRules/length.js
new file mode 100644
index 0000000000..cc2ea11067
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/length.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules
+description: Intl.PluralRules.length.
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules, 'length', {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/name.js b/js/src/tests/test262/intl402/PluralRules/name.js
new file mode 100644
index 0000000000..fdcfd0ae70
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/name.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules
+description: Intl.PluralRules.name is "PluralRules"
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules, 'name', {
+ value: 'PluralRules',
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/prop-desc.js
new file mode 100644
index 0000000000..d21a3fec88
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules
+description: >
+ "PluralRules" property of Intl.
+info: |
+ Intl.PluralRules (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl, 'PluralRules', {
+ writable: true,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/PluralRules/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..c5834ea464
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/proto-from-ctor-realm.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.PluralRules ( [ locales [ , options ] ] )
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let pluralRules be ? OrdinaryCreateFromConstructor(newTarget, "%PluralRulesPrototype%", « ... »).
+ 3. Return ? InitializePluralRules(pluralRules, locales, options).
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var pr;
+
+newTarget.prototype = undefined;
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = false;
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = '';
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 0;
+pr = Reflect.construct(Intl.PluralRules, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(pr), other.Intl.PluralRules.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/bind.js b/js/src/tests/test262/intl402/PluralRules/prototype/bind.js
new file mode 100644
index 0000000000..9b5142866c
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/bind.js
@@ -0,0 +1,28 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-pluralrules-prototype-object
+description: >
+ Tests that Intl.PluralRules.prototype functions throw a TypeError if
+ called on a non-object value or an object that hasn't been
+ initialized as a PluralRules.
+author: Zibi Braniecki
+---*/
+
+var functions = {
+ select: Intl.PluralRules.prototype.select,
+ resolvedOptions: Intl.PluralRules.prototype.resolvedOptions
+};
+var invalidTargets = [undefined, null, true, 0, "PluralRules", [], {}];
+
+Object.getOwnPropertyNames(functions).forEach(function (functionName) {
+ var f = functions[functionName];
+ invalidTargets.forEach(function (target) {
+ assert.throws(TypeError, function () {
+ f.call(target);
+ }, "Calling " + functionName + " on " + target + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/builtins.js b/js/src/tests/test262/intl402/PluralRules/prototype/builtins.js
new file mode 100644
index 0000000000..22bc883a14
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/builtins.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-pluralrules-prototype-object
+description: >
+ Tests that Intl.PluralRules.prototype meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Zibi Braniecki
+---*/
+
+assert(Object.isExtensible(Intl.PluralRules.prototype), "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.PluralRules.prototype), Object.prototype,
+ "Built-in prototype objects must have Object.prototype as their prototype.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/constructor/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/constructor/main.js b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/main.js
new file mode 100644
index 0000000000..8a3bb64e8c
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/main.js
@@ -0,0 +1,14 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.constructor
+description: >
+ Tests that Intl.PluralRules.prototype is an object that has been
+ initialized as an Intl.PluralRules.
+author: Zibi Braniecki
+---*/
+
+assert.sameValue(Intl.PluralRules.prototype.constructor, Intl.PluralRules, "Intl.PluralRules.prototype.constructor is not the same as Intl.PluralRules");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..cada231f2e
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype.constructor
+description: >
+ "constructor" property of Intl.PluralRules.prototype.
+info: |
+ Intl.PluralRules.prototype.constructor
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype, "constructor", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/constructor/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/properties.js b/js/src/tests/test262/intl402/PluralRules/prototype/properties.js
new file mode 100644
index 0000000000..d30b187e6c
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/properties.js
@@ -0,0 +1,17 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-pluralrules-prototype-object
+description: Tests that Intl.PluralRules.prototype has the required attributes.
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/prototype.js b/js/src/tests/test262/intl402/PluralRules/prototype/prototype.js
new file mode 100644
index 0000000000..6329c09f5f
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/prototype.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-properties-of-intl-pluralrules-prototype-object
+description: >
+ Tests that Intl.PluralRules.prototype is not an object that has been
+ initialized as an Intl.PluralRules.
+author: Zibi Braniecki
+---*/
+
+// test by calling a function that fails if "this" is not an object
+// initialized as an Intl.PluralRules
+assert.throws(TypeError, function() {
+ Intl.PluralRules.prototype.select(0);
+}, "Intl.PluralRules.prototype is not an object that has been initialized as an Intl.PluralRules");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/builtins.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/builtins.js
new file mode 100644
index 0000000000..72e8ef9d1f
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/builtins.js
@@ -0,0 +1,30 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.resolvedOptions
+description: >
+ Tests that Intl.PluralRules.prototype.resolvedOptions meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Zibi Braniecki
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules.prototype.resolvedOptions), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.PluralRules.prototype.resolvedOptions),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.PluralRules.prototype.resolvedOptions), Function.prototype);
+
+assert.sameValue(Intl.PluralRules.prototype.resolvedOptions.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.PluralRules.prototype.resolvedOptions), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..74b670ed70
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype.resolvedoptions
+description: >
+ Intl.PluralRules.prototype.resolvedOptions.length is 0.
+info: |
+ Intl.PluralRules.prototype.resolvedOptions ()
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..0bb3ab8903
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/name.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.resolvedOptions.name
+description: Intl.PluralRules.resolvedOptions.name is "resolvedOptions"
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..4d39e632eb
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/order.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.numberformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.NumberFormat-unified]
+---*/
+
+const options = new Intl.PluralRules([], {
+ "minimumSignificantDigits": 1,
+ "maximumSignificantDigits": 2,
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "type",
+ "minimumIntegerDigits",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits",
+ "pluralCategories",
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/pluralCategories.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/pluralCategories.js
new file mode 100644
index 0000000000..9fb31b7d4f
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/pluralCategories.js
@@ -0,0 +1,32 @@
+// Copyright 2018 Igalia S.L. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.resolvedOptions
+description: >
+ Tests that Intl.PluralRules.prototype.resolvedOptions creates a new array
+ for the pluralCategories property on every call.
+includes: [propertyHelper.js, compareArray.js]
+features: [Array.prototype.includes]
+---*/
+
+const allowedValues = ["zero", "one", "two", "few", "many", "other"];
+
+const pluralrules = new Intl.PluralRules();
+const options1 = pluralrules.resolvedOptions();
+const options2 = pluralrules.resolvedOptions();
+
+assert.notSameValue(options1.pluralCategories, options2.pluralCategories, "Should have different arrays");
+assert.compareArray(options1.pluralCategories, options2.pluralCategories, "Arrays should have same values");
+
+for (const category of options1.pluralCategories) {
+ assert(allowedValues.includes(category), `Found ${category}, expected one of ${allowedValues}`);
+}
+
+verifyProperty(options1, "pluralCategories", {
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..9cacaeed90
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype.resolvedoptions
+description: >
+ "resolvedOptions" property of Intl.PluralRules.prototype.
+info: |
+ Intl.PluralRules.prototype.resolvedOptions ()
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/properties.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/properties.js
new file mode 100644
index 0000000000..1db3cb1927
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/properties.js
@@ -0,0 +1,38 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.resolvedOptions
+description: >
+ Tests that the object returned by
+ Intl.PluralRules.prototype.resolvedOptions has the right
+ properties.
+author: Zibi Braniecki
+includes: [testIntl.js, propertyHelper.js]
+---*/
+
+var actual = new Intl.PluralRules().resolvedOptions();
+
+var actual2 = new Intl.PluralRules().resolvedOptions();
+assert.notSameValue(actual2, actual, "resolvedOptions returned the same object twice.");
+
+// this assumes the default values where the specification provides them
+assert(isCanonicalizedStructurallyValidLanguageTag(actual.locale),
+ "Invalid locale: " + actual.locale);
+assert.sameValue(actual.type, "cardinal");
+assert.sameValue(actual.minimumIntegerDigits, 1);
+assert.sameValue(actual.minimumFractionDigits, 0);
+assert.sameValue(actual.maximumFractionDigits, 3);
+
+var dataPropertyDesc = { writable: true, enumerable: true, configurable: true };
+verifyProperty(actual, "locale", dataPropertyDesc);
+verifyProperty(actual, "type", dataPropertyDesc);
+verifyProperty(actual, "currency", undefined);
+verifyProperty(actual, "currencyDisplay", undefined);
+verifyProperty(actual, "minimumIntegerDigits", dataPropertyDesc);
+verifyProperty(actual, "minimumFractionDigits", dataPropertyDesc);
+verifyProperty(actual, "maximumFractionDigits", dataPropertyDesc);
+verifyProperty(actual, "minimumSignificantDigits", undefined);
+verifyProperty(actual, "maximumSignificantDigits", undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/return-keys-order-default.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/return-keys-order-default.js
new file mode 100644
index 0000000000..e1dda53bc6
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/return-keys-order-default.js
@@ -0,0 +1,39 @@
+// Copyright 2023 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-intl.pluralrules.prototype.resolvedoptions
+description: order of property keys for the object returned by resolvedOptions()
+features: [Intl.NumberFormat-v3]
+includes: [compareArray.js]
+---*/
+
+const allKeys = [
+ 'locale',
+ 'type',
+ 'minimumIntegerDigits',
+ 'minimumFractionDigits',
+ 'maximumFractionDigits',
+ 'minimumSignificantDigits',
+ 'maximumSignificantDigits',
+ 'pluralCategories',
+ 'roundingIncrement',
+ 'roundingMode',
+ 'roundingPriority',
+ 'trailingZeroDisplay'
+];
+
+const options = [
+ { },
+ { minimumSignificantDigits: 3 },
+ { minimumFractionDigits: 3 },
+];
+options.forEach((option) => {
+ const nf = new Intl.PluralRules(undefined, option);
+ const resolved = nf.resolvedOptions();
+ const resolvedKeys = Reflect.ownKeys(resolved);
+ const expectedKeys = allKeys.filter(key => key in resolved);
+ assert.compareArray(resolvedKeys, expectedKeys,
+ 'resolvedOptions() property key order with options ' + JSON.stringify(options));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/resolvedOptions/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/length.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/length.js
new file mode 100644
index 0000000000..9b058ed2c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype.select
+description: >
+ Intl.PluralRules.prototype.select is 1.
+info: |
+ Intl.PluralRules.prototype.select( value )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.select, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/name.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/name.js
new file mode 100644
index 0000000000..1cb40c0345
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/name.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.select
+description: Intl.PluralRules.prototype.select.name is "select"
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.select, "name", {
+ value: "select",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/non-finite.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/non-finite.js
new file mode 100644
index 0000000000..14166d66ec
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/non-finite.js
@@ -0,0 +1,24 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.select
+description: Tests that select function returns "other" for non finite values.
+info: |
+ 1.1.4. ResolvePlural (pluralRules, n)
+ (...)
+ 1.1.4_3. If isFinite(n) is false, then
+ 1.1.4_3.a. Return "other".
+author: Zibi Braniecki
+
+---*/
+
+var invalidValues = [NaN, Infinity, -Infinity];
+
+var pr = new Intl.PluralRules();
+
+invalidValues.forEach(function (value) {
+ assert.sameValue(pr.select(value), "other");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/prop-desc.js
new file mode 100644
index 0000000000..487c60a182
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype.select
+description: >
+ "select" property of Intl.PluralRules.prototype.
+info: |
+ Intl.PluralRules.prototype.select( value )
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype, "select", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/select/tainting.js b/js/src/tests/test262/intl402/PluralRules/prototype/select/tainting.js
new file mode 100644
index 0000000000..19dc9eddc5
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/select/tainting.js
@@ -0,0 +1,22 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-intl-pluralrules-abstracts
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+info: |
+ 1.1.1. InitializePluralRules (pluralRules, locales, options)
+ (...)
+ 1.1.1_6. Let t be ? GetOption(options, "type", "string", « "cardinal", "ordinal" », "cardinal").
+author: Zibi Braniecki
+includes: [testIntl.js]
+---*/
+
+taintProperties(["type"]);
+
+var pr = new Intl.PluralRules();
+var time = pr.select(9);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/argument-tonumber-throws.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/argument-tonumber-throws.js
new file mode 100644
index 0000000000..2d37519d26
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/argument-tonumber-throws.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: >
+ "selectRange" basic tests when argument cannot be converted using ToNumber
+info: |
+ Intl.PluralRules.prototype.selectRange ( start, end )
+ (...)
+ 4. Let x be ? ToNumber(start).
+ 5. Let y be ? ToNumber(end).
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules("en-US");
+
+// Throw if arguments cannot be cast toNumber
+assert.throws(TypeError, () => { pr.selectRange(Symbol(102), 201) });
+assert.throws(TypeError, () => { pr.selectRange(102,Symbol(201)) });
+assert.throws(TypeError, () => { pr.selectRange(23n, 100) });
+assert.throws(TypeError, () => { pr.selectRange(100, 23n) });
+assert.throws(TypeError, () => { pr.selectRange(23n, 23n) });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/default-en-us.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/default-en-us.js
new file mode 100644
index 0000000000..75222d5c9b
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/default-en-us.js
@@ -0,0 +1,16 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Intl.PluralRules.prototype.selectRange default behaviour returning "few" or "other"
+locale: [en-US]
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules("en-US");
+
+assert.sameValue(pr.selectRange(102, 201), "other");
+assert.sameValue(pr.selectRange(200, 200), "other");
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/invoked-as-func.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/invoked-as-func.js
new file mode 100644
index 0000000000..27a9913106
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/invoked-as-func.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: basic tests internal slot initialization and call receiver errors
+info: |
+ Intl.PluralRules.prototype.selectRange(start, end )
+ (...)
+ 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]])
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules();
+
+// Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]).
+let sr = pr['selectRange'];
+
+assert.sameValue(typeof sr, 'function');
+assert.throws(TypeError, () => { sr(1, 23) });
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/length.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/length.js
new file mode 100644
index 0000000000..56f550c301
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/length.js
@@ -0,0 +1,18 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: Intl.PluralRules.prototype.selectRange.length is 2
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.selectRange, 'length', {
+ value: 2,
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/name.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/name.js
new file mode 100644
index 0000000000..d58906b69e
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/name.js
@@ -0,0 +1,19 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: Intl.PluralRules.prototype.selectRange.name is "selectRange"
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype.selectRange, 'name', {
+ value: 'selectRange',
+ enumerable: false,
+ writable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/nan-arguments-throws.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/nan-arguments-throws.js
new file mode 100644
index 0000000000..6a216ef218
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/nan-arguments-throws.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: >
+ "selectRange" Throws a RangeError if some of arguments is cast to NaN
+info: |
+ Intl.PluralRules.prototype.selectRange ( start, end )
+ (...)
+ WIP: https://github.com/tc39/proposal-intl-numberformat-v3/pull/76
+
+
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules();
+
+assert.throws(RangeError, () => { pr.selectRange(NaN, 100) }, "NaN/Number");
+assert.throws(RangeError, () => { pr.selectRange(100, NaN) }, "Number/NaN");
+assert.throws(RangeError, () => { pr.selectRange(NaN, NaN) }, "NaN/NaN");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/prop-desc.js
new file mode 100644
index 0000000000..7a770e99c0
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/prop-desc.js
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: Property descriptor of Intl.PluralRules.prototype.selectRange
+includes: [propertyHelper.js]
+features: [Intl.NumberFormat-v3]
+---*/
+
+assert.sameValue(
+ typeof Intl.PluralRules.prototype.selectRange,
+ 'function',
+ '`typeof Intl.PluralRules.prototype.selectRange` is `function`'
+);
+
+verifyProperty(Intl.PluralRules.prototype, 'selectRange', {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/undefined-arguments-throws.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/undefined-arguments-throws.js
new file mode 100644
index 0000000000..0d85153681
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/undefined-arguments-throws.js
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRange
+description: >
+ "selectRange" basic tests when arguments are undefined throw a TypeError exception.
+info: |
+ Intl.PluralRules.prototype.selectRange ( start, end )
+ (...)
+ 3. If start is undefined or end is undefined, throw a TypeError exception.
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules();
+
+// 1. If arguments are undefined throw a TypeError exception.
+assert.throws(TypeError, () => { pr.selectRange(undefined, 201) });
+assert.throws(TypeError, () => { pr.selectRange(102, undefined) });
+assert.throws(TypeError, () => { pr.selectRange(undefined, undefined)});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/x-greater-than-y-not-throws.js b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/x-greater-than-y-not-throws.js
new file mode 100644
index 0000000000..410baa4df4
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/selectRange/x-greater-than-y-not-throws.js
@@ -0,0 +1,18 @@
+// Copyright 2022 Google, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.prototype.selectRang
+description: >
+ "selectRange" basic tests when argument x > y, return a string.
+info: |
+ 1.1.6 ResolvePluralRange ( pluralRules, x, y )
+features: [Intl.NumberFormat-v3]
+---*/
+
+const pr = new Intl.PluralRules();
+
+// 1. If x > y, return a string.
+assert.sameValue(typeof pr.selectRange(201, 102), "string", "should not throw RangeError");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-changed-tag.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-changed-tag.js
new file mode 100644
index 0000000000..20b48fcc35
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-changed-tag.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype-tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.PluralRules.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.PluralRules.prototype [ @@toStringTag ]
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+---*/
+
+Object.defineProperty(Intl.PluralRules.prototype, Symbol.toStringTag, {
+ value: "test262",
+});
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules.prototype), "[object test262]");
+assert.sameValue(Object.prototype.toString.call(new Intl.PluralRules()), "[object test262]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-removed-tag.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-removed-tag.js
new file mode 100644
index 0000000000..02f835ff2f
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString-removed-tag.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype-tostringtag
+description: >
+ Object.prototype.toString doesn't special-case neither Intl.PluralRules instances nor its prototype.
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+features: [Symbol.toStringTag]
+---*/
+
+delete Intl.PluralRules.prototype[Symbol.toStringTag];
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules.prototype), "[object Object]");
+assert.sameValue(Object.prototype.toString.call(new Intl.PluralRules()), "[object Object]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..e5a30f15e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toString.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype-tostringtag
+description: >
+ Object.prototype.toString utilizes Intl.PluralRules.prototype[@@toStringTag].
+info: |
+ Object.prototype.toString ( )
+
+ [...]
+ 14. Else, let builtinTag be "Object".
+ 15. Let tag be ? Get(O, @@toStringTag).
+ 16. If Type(tag) is not String, set tag to builtinTag.
+ 17. Return the string-concatenation of "[object ", tag, and "]".
+
+ Intl.PluralRules.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.PluralRules".
+features: [Symbol.toStringTag]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules.prototype), "[object Intl.PluralRules]");
+assert.sameValue(Object.prototype.toString.call(new Intl.PluralRules()), "[object Intl.PluralRules]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..cdac2facc9
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.prototype-tostringtag
+description: >
+ Property descriptor of Intl.PluralRules.prototype[@@toStringTag].
+info: |
+ Intl.PluralRules.prototype [ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the String value "Intl.PluralRules".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+features: [Symbol.toStringTag]
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.prototype, Symbol.toStringTag, {
+ value: "Intl.PluralRules",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/shell.js b/js/src/tests/test262/intl402/PluralRules/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/shell.js
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/arguments.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/arguments.js
new file mode 100644
index 0000000000..bf6e487d6e
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/arguments.js
@@ -0,0 +1,16 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.supportedLocalesOf
+description: >
+ Tests that Intl.PluralRules.supportedLocalesOf doesn't access
+ arguments that it's not given.
+author: Zibi Braniecki
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Object.prototype, "1");
+new Intl.PluralRules("und");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..b4c149490f
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.supportedlocalesof
+description: >
+ Intl.PluralRules.supportedLocalesOf.length is 1.
+info: |
+ Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/main.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/main.js
new file mode 100644
index 0000000000..23afcb1099
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/main.js
@@ -0,0 +1,25 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.supportedLocalesOf
+description: >
+ Tests that Intl.PluralRules has a supportedLocalesOf property, and
+ it works as planned.
+author: Zibi Braniecki
+---*/
+
+var defaultLocale = new Intl.PluralRules().resolvedOptions().locale;
+var notSupported = 'zxx'; // "no linguistic content"
+var requestedLocales = [defaultLocale, notSupported];
+
+var supportedLocales;
+
+assert(Intl.PluralRules.hasOwnProperty('supportedLocalesOf'), "Intl.PluralRules doesn't have a supportedLocalesOf property.");
+
+supportedLocales = Intl.PluralRules.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..e6f9f83251
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/name.js
@@ -0,0 +1,18 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.supportedLocalesOf
+description: Tests that Intl.PluralRules.supportedLocalesOf.name is "supportedLocalesOf"
+author: Zibi Braniecki
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..f410605873
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.pluralrules.supportedlocalesof
+description: >
+ "supportedLocalesOf" property of Intl.PluralRules.
+info: |
+ Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(Intl.PluralRules, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/supportedLocalesOf.js b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/supportedLocalesOf.js
new file mode 100644
index 0000000000..1bff8cdb1c
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/supportedLocalesOf/supportedLocalesOf.js
@@ -0,0 +1,30 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules.supportedLocalesOf
+description: >
+ Tests that Intl.PluralRules.supportedLocalesOf meets the requirements for
+ built-in objects defined by the introduction of chapter 17 of the
+ ECMAScript Language Specification.
+author: Zibi Braniecki
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.PluralRules.supportedLocalesOf), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(Intl.PluralRules.supportedLocalesOf),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(Intl.PluralRules.supportedLocalesOf), Function.prototype);
+
+assert.sameValue(Intl.PluralRules.supportedLocalesOf.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(Intl.PluralRules.supportedLocalesOf), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/PluralRules/undefined-newtarget-throws.js b/js/src/tests/test262/intl402/PluralRules/undefined-newtarget-throws.js
new file mode 100644
index 0000000000..77c82be1de
--- /dev/null
+++ b/js/src/tests/test262/intl402/PluralRules/undefined-newtarget-throws.js
@@ -0,0 +1,27 @@
+// Copyright 2016 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.PluralRules
+description: Tests that PluralRules throws when called as a function
+author: Zibi Braniecki
+includes: [testIntl.js]
+---*/
+
+assert.throws(TypeError, function() {
+ Intl.PluralRules();
+}, "Intl.PluralRules throws when called as a function");
+
+assert.throws(TypeError, function() {
+ Intl.PluralRules.call(undefined);
+}, "Intl.PluralRules throws when called as a function with |undefined| as this-value");
+
+testWithIntlConstructors(function (Constructor) {
+ var obj = new Constructor();
+
+ assert.throws(TypeError, function() {
+ Intl.PluralRules.call(obj)
+ }, "Intl.PluralRules throws when called as a function with an Intl-object as this-value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-invalid.js
new file mode 100644
index 0000000000..dc5ea2b632
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-invalid.js
@@ -0,0 +1,20 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks error cases for the locales argument to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+includes: [testIntl.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, function() { new Intl.RelativeTimeFormat(locales) });
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js
new file mode 100644
index 0000000000..91a1721531
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js
@@ -0,0 +1,45 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks various cases for the locales argument to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const defaultLocale = new Intl.RelativeTimeFormat().resolvedOptions().locale;
+
+const tests = [
+ [undefined, defaultLocale, "undefined"],
+ ["EN", "en", "Single value"],
+ [[], defaultLocale, "Empty array"],
+ [["en", "EN"], "en", "Duplicate value (canonical first)"],
+ [["EN", "en"], "en", "Duplicate value (canonical last)"],
+ [{ 0: "DE", length: 0 }, defaultLocale, "Object with zero length"],
+ [{ 0: "DE", length: 1 }, "de", "Object with length"],
+];
+
+const errorTests = [
+ [["en-GB-oed"], "Grandfathered"],
+ [["x-private"], "Private", ["lookup"]],
+];
+
+for (const [locales, expected, name, matchers = ["best fit", "lookup"]] of tests) {
+ for (const matcher of matchers) {
+ const rtf = new Intl.RelativeTimeFormat(locales, {localeMatcher: matcher});
+ assert.sameValue(rtf.resolvedOptions().locale, expected, name);
+ }
+}
+
+for (const [locales, name, matchers = ["best fit", "lookup"]] of errorTests) {
+ for (const matcher of matchers) {
+ assert.throws(RangeError, function() {
+ new Intl.RelativeTimeFormat(locales, {localeMatcher: matcher});
+ }, name);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js
new file mode 100644
index 0000000000..34e34faaed
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/newtarget-undefined.js
@@ -0,0 +1,29 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.RelativeTimeFormat
+description: >
+ Verifies the NewTarget check for Intl.RelativeTimeFormat.
+info: |
+ Intl.RelativeTimeFormat ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+assert.throws(TypeError, function() {
+ Intl.RelativeTimeFormat();
+});
+
+assert.throws(TypeError, function() {
+ Intl.RelativeTimeFormat("en");
+});
+
+assert.throws(TypeError, function() {
+ Intl.RelativeTimeFormat("not-valid-tag");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-invalid.js
new file mode 100644
index 0000000000..94db7ed97f
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-invalid.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of a null options argument to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 5. Else
+ a. Let options be ? ToObject(options).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+assert.throws(TypeError, function() { new Intl.RelativeTimeFormat([], null) })
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..f567f29a04
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-localeMatcher-invalid.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of invalid value for the localeMatcher option to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.RelativeTimeFormat([], {"localeMatcher": invalidOption});
+ }, `${invalidOption} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-invalid.js
new file mode 100644
index 0000000000..4451d7be14
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-invalid.js
@@ -0,0 +1,46 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ Checks error cases for the options argument to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+
+ ...
+ 8. If numberingSystem is not undefined, then
+ a. If numberingSystem does not match the type sequence (from UTS 35 Unicode Locale Identifier, section 3.2), throw a RangeError exception.
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+/*
+ alphanum = (ALPHA / DIGIT) ; letters and numbers
+ numberingSystem = (3*8alphanum) *("-" (3*8alphanum))
+*/
+const invalidNumberingSystemOptions = [
+ "",
+ "a",
+ "ab",
+ "abcdefghi",
+ "abc-abcdefghi",
+ "!invalid!",
+ "-latn-",
+ "latn-",
+ "latn--",
+ "latn-ca",
+ "latn-ca-",
+ "latn-ca-gregory",
+ "latné",
+ "latn编号",
+];
+for (const numberingSystem of invalidNumberingSystemOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.RelativeTimeFormat('en', {numberingSystem});
+ }, `new Intl.RelativeTimeFormat("en", {numberingSystem: "${numberingSystem}"}) throws RangeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-valid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-valid.js
new file mode 100644
index 0000000000..aa9d5038f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numberingSystem-valid.js
@@ -0,0 +1,29 @@
+// Copyright 2018 André Bargull; Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks valid cases for the options argument to the RelativeTimeFormat constructor.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+const validNumberingSystemOptions = [
+ "abc",
+ "abcd",
+ "abcde",
+ "abcdef",
+ "abcdefg",
+ "abcdefgh",
+ "12345678",
+ "1234abcd",
+ "1234abcd-abc123",
+];
+
+for (const numberingSystem of validNumberingSystemOptions) {
+ const rtf = new Intl.RelativeTimeFormat("en", {numberingSystem});
+ assert.sameValue(rtf.resolvedOptions().numberingSystem, "latn");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-invalid.js
new file mode 100644
index 0000000000..3b02910050
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-invalid.js
@@ -0,0 +1,33 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of invalid value for the numeric option to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 16. Let numeric be ? GetOption(options, "numeric", "string", «"always", "auto"», "always").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Always",
+ "ALWAYS",
+ "always\0",
+ "Auto",
+ "AUTO",
+ "auto\0",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.RelativeTimeFormat([], {"numeric": invalidOption});
+ }, `${invalidOption} is an invalid numeric option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-valid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-valid.js
new file mode 100644
index 0000000000..d831b94128
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-numeric-valid.js
@@ -0,0 +1,27 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of valid values for the numeric option to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 16. Let numeric be ? GetOption(options, "numeric", "string", «"always", "auto"», "always").
+ 17. Set relativeTimeFormat.[[Numeric]] to numeric.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const validOptions = [
+ [undefined, "always"],
+ ["always", "always"],
+ ["auto", "auto"],
+ [{ toString() { return "auto"; } }, "auto"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const tf = new Intl.RelativeTimeFormat([], {"numeric": validOption});
+ const resolvedOptions = tf.resolvedOptions();
+ assert.sameValue(resolvedOptions.numeric, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-order.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-order.js
new file mode 100644
index 0000000000..e8684f2121
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-order.js
@@ -0,0 +1,68 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks the order of operations on the options argument to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+ 14. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
+ 16. Let numeric be ? GetOption(options, "numeric", "string", «"always", "auto"», "always").
+includes: [compareArray.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const callOrder = [];
+
+new Intl.RelativeTimeFormat([], {
+ get localeMatcher() {
+ callOrder.push("localeMatcher");
+ return {
+ toString() {
+ callOrder.push("localeMatcher toString");
+ return "best fit";
+ }
+ };
+ },
+ get style() {
+ callOrder.push("style");
+ return {
+ toString() {
+ callOrder.push("style toString");
+ return "long";
+ }
+ };
+ },
+ get numberingSystem() {
+ callOrder.push("numberingSystem");
+ return {
+ toString() {
+ callOrder.push("numberingSystem toString");
+ return "abc";
+ }
+ };
+ },
+ get numeric() {
+ callOrder.push("numeric");
+ return {
+ toString() {
+ callOrder.push("numeric toString");
+ return "always";
+ }
+ };
+ },
+});
+
+assert.compareArray(callOrder, [
+ "localeMatcher",
+ "localeMatcher toString",
+ "numberingSystem",
+ "numberingSystem toString",
+ "style",
+ "style toString",
+ "numeric",
+ "numeric toString",
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-proto.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-proto.js
new file mode 100644
index 0000000000..c47805f02b
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-proto.js
@@ -0,0 +1,90 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: |
+ Checks that the RelativeTimeFormat constructor does not cause the
+ NumberFormat and PluralRules constructors to get properties off
+ Object.prototype through the options objects it creates.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 20. Let nfOptions be ObjectCreate(null).
+ 25. Let prOptions be ObjectCreate(null).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+Object.defineProperties(Object.prototype, {
+ // NumberFormat & PluralRules
+ "localeMatcher": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: localeMatcher");
+ },
+ },
+
+ "minimumIntegerDigits": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: minimumIntegerDigits");
+ },
+ },
+
+ "minimumFractionDigits": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: minimumFractionDigits");
+ },
+ },
+
+ "maximumFractionDigits": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: maximumFractionDigits");
+ },
+ },
+
+ "minimumSignificantDigits": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: minimumSignificantDigits");
+ },
+ },
+
+ "maximumSignificantDigits": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: maximumSignificantDigits");
+ },
+ },
+
+ // NumberFormat
+ "style": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: style");
+ },
+ },
+
+ "currency": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: currency");
+ },
+ },
+
+ "currencyDisplay": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: currencyDisplay");
+ },
+ },
+
+ "useGrouping": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: useGrouping");
+ },
+ },
+
+ // PluralRules
+ "type": {
+ "get": function() {
+ throw new Test262Error("Should not call getter on Object.prototype: type");
+ },
+ },
+});
+
+new Intl.RelativeTimeFormat();
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-invalid.js
new file mode 100644
index 0000000000..7b397bed71
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-invalid.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of invalid value for the style option to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 14. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Long",
+ "LONG",
+ "long\0",
+ "Short",
+ "SHORT",
+ "short\0",
+ "Narrow",
+ "NARROW",
+ "narrow\0",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.RelativeTimeFormat([], {"style": invalidOption});
+ }, `${invalidOption} is an invalid style option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-valid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-valid.js
new file mode 100644
index 0000000000..1e53694ae7
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-style-valid.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of valid values for the style option to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+ 14. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
+ 15. Set relativeTimeFormat.[[Style]] to s.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const validOptions = [
+ [undefined, "long"],
+ ["long", "long"],
+ ["short", "short"],
+ ["narrow", "narrow"],
+ [{ toString() { return "narrow"; } }, "narrow"],
+];
+
+for (const [validOption, expected] of validOptions) {
+ const tf = new Intl.RelativeTimeFormat([], {"style": validOption});
+ const resolvedOptions = tf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, expected);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-throwing-getters.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-throwing-getters.js
new file mode 100644
index 0000000000..f06e962f64
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-throwing-getters.js
@@ -0,0 +1,119 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-InitializeRelativeTimeFormat
+description: Checks the propagation of exceptions from the options for the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat
+
+ 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+ ...
+ 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
+ ...
+ 16. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
+ ...
+ 18. Let numeric be ? GetOption(options, "numeric", "string", «"always", "auto"», "always").
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ 2. If value is not undefined, then
+ a. Assert: type is "boolean" or "string".
+ b. If type is "boolean", then
+ i. Let value be ToBoolean(value).
+ c. If type is "string", then
+ i. Let value be ? ToString(value).
+ d. If values is not undefined, then
+ i. If values does not contain an element equal to value, throw a RangeError exception.
+ e. Return value.
+ 3. Else, return fallback.
+features: [Intl.RelativeTimeFormat]
+includes: [compareArray.js]
+---*/
+
+function CustomError() {}
+
+const o1 = {
+ get localeMatcher() {
+ throw new CustomError();
+ },
+ get numberingSystem() {
+ throw "should not get the numberingSystem option before localeMatcher";
+ },
+ get style() {
+ throw "should not get the style option before localeMatcher";
+ },
+ get numeric() {
+ throw "should not get the numeric option before localeMatcher";
+ }
+};
+
+const o2captures = [];
+const o2 = {
+ get localeMatcher() {
+ o2captures.push('localeMatcher');
+ },
+ get numberingSystem() {
+ throw new CustomError();
+ },
+ get style() {
+ throw "should not get the style option before numberingSystem";
+ },
+ get numeric() {
+ throw "should not get the numeric option before numberingSystem";
+ }
+};
+
+const o3captures = [];
+const o3 = {
+ get localeMatcher() {
+ o3captures.push('localeMatcher');
+ },
+ get numberingSystem() {
+ o3captures.push('numberingSystem');
+ },
+ get style() {
+ throw new CustomError();
+ },
+ get numeric() {
+ throw "should not get the numeric option before style";
+ }
+};
+
+const o4captures = [];
+const o4 = {
+ get localeMatcher() {
+ o4captures.push('localeMatcher');
+ },
+ get numberingSystem() {
+ o4captures.push('numberingSystem');
+ },
+ get style() {
+ o4captures.push('style');
+ },
+ get numeric() {
+ throw new CustomError();
+ }
+};
+
+assert.throws(CustomError, () => {
+ new Intl.RelativeTimeFormat("en", o1);
+}, `Exception from localeMatcher getter should be propagated`);
+
+assert.throws(CustomError, () => {
+ new Intl.RelativeTimeFormat("en", o2);
+}, `Exception from numberingSystem getter should be propagated`);
+assert.compareArray(o2captures, ['localeMatcher']);
+
+assert.throws(CustomError, () => {
+ new Intl.RelativeTimeFormat("en", o3);
+}, `Exception from style getter should be propagated`);
+assert.compareArray(o3captures, ['localeMatcher', 'numberingSystem']);
+
+assert.throws(CustomError, () => {
+ new Intl.RelativeTimeFormat("en", o4);
+}, `Exception from numeric getter should be propagated`);
+assert.compareArray(o4captures, ['localeMatcher', 'numberingSystem', 'style']);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject-prototype.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject-prototype.js
new file mode 100644
index 0000000000..2c352b2dd0
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject-prototype.js
@@ -0,0 +1,37 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of non-object option arguments to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+features: [Intl.RelativeTimeFormat]
+---*/
+
+Object.defineProperties(Object.prototype, {
+ "style": {
+ value: "short",
+ },
+ "numeric": {
+ value: "auto",
+ },
+})
+
+const optionsArguments = [
+ true,
+ "test",
+ 7,
+ Symbol(),
+];
+
+for (const options of optionsArguments) {
+ const rtf = new Intl.RelativeTimeFormat([], options);
+ const resolvedOptions = rtf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, "short",
+ `options argument ${String(options)} should yield the correct value for "style"`);
+ assert.sameValue(resolvedOptions.numeric, "auto",
+ `options argument ${String(options)} should yield the correct value for "numeric"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject.js
new file mode 100644
index 0000000000..872308d078
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-toobject.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of non-object option arguments to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const optionsArguments = [
+ true,
+ "test",
+ 7,
+ Symbol(),
+];
+
+for (const options of optionsArguments) {
+ const rtf = new Intl.RelativeTimeFormat([], options);
+ const resolvedOptions = rtf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, "long",
+ `options argument ${String(options)} should yield the correct value for "style"`);
+ assert.sameValue(resolvedOptions.numeric, "always",
+ `options argument ${String(options)} should yield the correct value for "numeric"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-undefined.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-undefined.js
new file mode 100644
index 0000000000..1953dd1cdc
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/options-undefined.js
@@ -0,0 +1,40 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks handling of non-object option arguments to the RelativeTimeFormat constructor.
+info: |
+ InitializeRelativeTimeFormat (relativeTimeFormat, locales, options)
+features: [Intl.RelativeTimeFormat]
+---*/
+
+Object.defineProperties(Object.prototype, {
+ style: {
+ get() {
+ throw new Error("Should not call style getter");
+ }
+ },
+ numeric: {
+ get() {
+ throw new Error("Should not call numeric getter");
+ }
+ },
+})
+
+const optionsArguments = [
+ [],
+ [[]],
+ [[], undefined],
+];
+
+for (const args of optionsArguments) {
+ const rtf = new Intl.RelativeTimeFormat(...args);
+ const resolvedOptions = rtf.resolvedOptions();
+ assert.sameValue(resolvedOptions.style, "long",
+ `Calling with ${args.length} empty arguments should yield the fallback value for "style"`);
+ assert.sameValue(resolvedOptions.numeric, "always",
+ `Calling with ${args.length} empty arguments should yield the fallback value for "numeric"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..5f71812c17
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/proto-from-ctor-realm.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.RelativeTimeFormat ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let relativeTimeFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%RelativeTimeFormatPrototype%", « ... »).
+ 3. Return ? InitializeRelativeTimeFormat(relativeTimeFormat, locales, options).
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.RelativeTimeFormat, cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var rtf;
+
+newTarget.prototype = undefined;
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = true;
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = '';
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = -1;
+rtf = Reflect.construct(Intl.RelativeTimeFormat, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(rtf), other.Intl.RelativeTimeFormat.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/subclassing.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/subclassing.js
new file mode 100644
index 0000000000..02b7b9fd2a
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/constructor/subclassing.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: Checks that RelativeTimeFormat can be subclassed.
+info: |
+ Intl.RelativeTimeFormat ( [ locales [ , options ] ] )
+
+ 2. Let relativeTimeFormat be ! OrdinaryCreateFromConstructor(NewTarget, "%RelativeTimeFormatPrototype%", « [[InitializedRelativeTimeFormat]] »).
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+class CustomRelativeTimeFormat extends Intl.RelativeTimeFormat {
+ constructor(locales, options) {
+ super(locales, options);
+ this.isCustom = true;
+ }
+}
+
+const locale = "de";
+const value = 7;
+const unit = "day";
+
+const real_rtf = new Intl.RelativeTimeFormat(locale);
+assert.sameValue(real_rtf.isCustom, undefined, "Custom property");
+
+const custom_rtf = new CustomRelativeTimeFormat(locale);
+assert.sameValue(custom_rtf.isCustom, true, "Custom property");
+
+assert.sameValue(custom_rtf.format(value, unit),
+ real_rtf.format(value, unit),
+ "Direct call");
+
+assert.sameValue(Intl.RelativeTimeFormat.prototype.format.call(custom_rtf, value, unit),
+ Intl.RelativeTimeFormat.prototype.format.call(real_rtf, value, unit),
+ "Indirect call");
+
+assert.sameValue(Object.getPrototypeOf(custom_rtf), CustomRelativeTimeFormat.prototype, "Prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/length.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/length.js
new file mode 100644
index 0000000000..752e277bc5
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ Checks the "length" property of the RelativeTimeFormat constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The RelativeTimeFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/name.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/name.js
new file mode 100644
index 0000000000..4dc57bf36f
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ Checks the "name" property of the RelativeTimeFormat constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat, "name", {
+ value: "RelativeTimeFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prop-desc.js
new file mode 100644
index 0000000000..af3bcf0aef
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prop-desc.js
@@ -0,0 +1,37 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ "RelativeTimeFormat" property of Intl.
+info: |
+ Intl.RelativeTimeFormat (...)
+
+ 7 Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat, "function");
+
+verifyProperty(Intl, "RelativeTimeFormat", {
+ value: Intl.RelativeTimeFormat,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prototype.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prototype.js
new file mode 100644
index 0000000000..665269f330
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/prototype.js
@@ -0,0 +1,19 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: The prototype of the Intl.RelativeTimeFormat constructor is %FunctionPrototype%.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Unless otherwise specified every built-in function object has the %FunctionPrototype% object as the initial value of its [[Prototype]] internal slot.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ Object.getPrototypeOf(Intl.RelativeTimeFormat),
+ Function.prototype,
+ "Object.getPrototypeOf(Intl.RelativeTimeFormat) equals the value of Function.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..7cd949a718
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/basic.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Google Inc., Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: >
+ Tests that Intl.RelativeTimeFormat has a supportedLocalesOf property,
+ and it works as planned.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "supportedLocalesOf should be supported.");
+
+const defaultLocale = new Intl.RelativeTimeFormat().resolvedOptions().locale;
+const notSupported = 'zxx'; // "no linguistic content"
+const requestedLocales = [defaultLocale, notSupported];
+
+const supportedLocales = Intl.RelativeTimeFormat.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, 'The length of supported locales list is not 1.');
+assert.sameValue(supportedLocales[0], defaultLocale, 'The default locale is not returned in the supported list.');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/branding.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/branding.js
new file mode 100644
index 0000000000..fa89f86ba0
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/branding.js
@@ -0,0 +1,34 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: >
+ Verifies there's no branding check for Intl.RelativeTimeFormat.supportedLocalesOf().
+info: |
+ Intl.RelativeTimeFormat.supportedLocalesOf ( locales [, options ])
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const supportedLocalesOf = Intl.RelativeTimeFormat.supportedLocalesOf;
+
+assert.sameValue(typeof supportedLocalesOf, "function");
+
+const thisValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.RelativeTimeFormat,
+ Intl.RelativeTimeFormat.prototype,
+];
+
+for (const thisValue of thisValues) {
+ const result = supportedLocalesOf.call(thisValue);
+ assert.sameValue(Array.isArray(result), true);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..2775259dc6
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/length.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: >
+ Checks the "length" property of Intl.RelativeTimeFormat.supportedLocalesOf().
+info: |
+ The value of the length property of the supportedLocalesOf method is 1.
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, has a length property whose value is an integer.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/locales-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/locales-invalid.js
new file mode 100644
index 0000000000..57cfa4a3ee
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/locales-invalid.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Checks error cases for the locales argument to the supportedLocalesOf function.
+info: |
+ Intl.RelativeTimeFormat.supportedLocalesOf ( locales [, options ])
+
+ 2. Let requestedLocales be CanonicalizeLocaleList(locales).
+includes: [testIntl.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "Should support Intl.RelativeTimeFormat.supportedLocalesOf.");
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, () => Intl.RelativeTimeFormat.supportedLocalesOf(locales));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..34c59814fd
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/name.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: >
+ Checks the "name" property of Intl.RelativeTimeFormat.supportedLocalesOf().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..854f9ca3d7
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
@@ -0,0 +1,36 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Checks handling of invalid values for the localeMatcher option to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "Should support Intl.RelativeTimeFormat.supportedLocalesOf.");
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ Intl.RelativeTimeFormat.supportedLocalesOf([], {"localeMatcher": invalidOption});
+ }, `${invalidOption} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-null.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-null.js
new file mode 100644
index 0000000000..be15359c46
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-null.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Checks handling of a null options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "Should support Intl.RelativeTimeFormat.supportedLocalesOf.");
+
+assert.throws(TypeError, function() {
+ Intl.RelativeTimeFormat.supportedLocalesOf([], null);
+}, "Should throw when passing null as the options argument");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-toobject.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-toobject.js
new file mode 100644
index 0000000000..036883f5ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-toobject.js
@@ -0,0 +1,43 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Checks handling of non-object options arguments to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "Should support Intl.RelativeTimeFormat.supportedLocalesOf.");
+
+let called;
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() {
+ ++called;
+ return "best fit";
+ }
+ }
+});
+
+const optionsArguments = [
+ true,
+ "test",
+ 7,
+ Symbol(),
+];
+
+for (const options of optionsArguments) {
+ called = 0;
+ const result = Intl.RelativeTimeFormat.supportedLocalesOf([], options);
+ assert.sameValue(Array.isArray(result), true, `Expected array from ${String(options)}`);
+ assert.sameValue(called, 1, `Expected one call from ${String(options)}`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-undefined.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-undefined.js
new file mode 100644
index 0000000000..9a0832ec14
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/options-undefined.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Checks handling of an undefined options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(typeof Intl.RelativeTimeFormat.supportedLocalesOf, "function",
+ "Should support Intl.RelativeTimeFormat.supportedLocalesOf.");
+
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() { throw new Error("Should not call localeMatcher getter"); }
+ }
+});
+
+assert.sameValue(Array.isArray(Intl.RelativeTimeFormat.supportedLocalesOf()), true, "No arguments");
+assert.sameValue(Array.isArray(Intl.RelativeTimeFormat.supportedLocalesOf([])), true, "One argument");
+assert.sameValue(Array.isArray(Intl.RelativeTimeFormat.supportedLocalesOf([], undefined)), true, "Two arguments");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..ea80acf8a3
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,31 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: >
+ Checks the "supportedLocalesOf" property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.supportedLocalesOf ( locales [, options ])
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.RelativeTimeFormat.supportedLocalesOf,
+ "function",
+ "typeof Intl.RelativeTimeFormat.supportedLocalesOf is function"
+);
+
+verifyProperty(Intl.RelativeTimeFormat, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/result-type.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/result-type.js
new file mode 100644
index 0000000000..8801c662c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/result-type.js
@@ -0,0 +1,35 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.supportedLocalesOf
+description: Verifies the type of the return value of Intl.RelativeTimeFormat.supportedLocalesOf().
+info: |
+ Intl.RelativeTimeFormat.supportedLocalesOf ( locales [, options ])
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const result = Intl.RelativeTimeFormat.supportedLocalesOf("en");
+assert.sameValue(Array.isArray(result), true,
+ "Array.isArray() should return true");
+assert.sameValue(Object.getPrototypeOf(result), Array.prototype,
+ "The prototype should be Array.prototype");
+assert.sameValue(Object.isExtensible(result), true,
+ "Object.isExtensible() should return true");
+
+assert.notSameValue(result.length, 0);
+for (let i = 0; i < result.length; ++i) {
+ verifyProperty(result, String(i), {
+ "writable": true,
+ "enumerable": true,
+ "configurable": true,
+ });
+}
+
+verifyProperty(result, "length", {
+ "enumerable": false,
+ "configurable": false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/constructor/supportedLocalesOf/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/instance/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/instance/extensibility.js b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/extensibility.js
new file mode 100644
index 0000000000..67ad4b9b83
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/extensibility.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ Intl.RelativeTimeFormat instance object extensibility
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Unless specified otherwise, the [[Extensible]] internal slot
+ of a built-in object initially has the value true.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ Object.isExtensible(new Intl.RelativeTimeFormat()),
+ true,
+ "Object.isExtensible(new Intl.RelativeTimeFormat()) returns true"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/instance/prototype.js b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/prototype.js
new file mode 100644
index 0000000000..f874db89f6
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/prototype.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat
+description: >
+ Intl.RelativeTimeFormat instance object is created from %RelativeTimeFormatPrototype%.
+info: |
+ Intl.RelativeTimeFormat ([ locales [ , options ]])
+
+ 2. Let relativeTimeFormat be ! OrdinaryCreateFromConstructor(NewTarget, "%RelativeTimeFormatPrototype%").
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const value = new Intl.RelativeTimeFormat();
+assert.sameValue(
+ Object.getPrototypeOf(value),
+ Intl.RelativeTimeFormat.prototype,
+ "Object.getPrototypeOf(value) equals the value of Intl.RelativeTimeFormat.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/instance/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/instance/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..b441c56413
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/prop-desc.js
@@ -0,0 +1,26 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.constructor
+description: Checks the "constructor" property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.constructor
+
+ The initial value of Intl.RelativeTimeFormat.prototype.constructor is %RelativeTimeFormat%.
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype, "constructor", {
+ value: Intl.RelativeTimeFormat,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/branding.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/branding.js
new file mode 100644
index 0000000000..fa7f890ad7
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/branding.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Verifies the branding check for the "format" function of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.format( value, unit )
+
+ 2. If Type(relativeTimeFormat) is not Object or relativeTimeFormat does not have an [[InitializedRelativeTimeFormat]] internal slot whose value is true, throw a TypeError exception.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const format = Intl.RelativeTimeFormat.prototype.format;
+
+assert.sameValue(typeof format, "function");
+
+assert.throws(TypeError, () => format.call(undefined), "undefined");
+assert.throws(TypeError, () => format.call(null), "null");
+assert.throws(TypeError, () => format.call(true), "true");
+assert.throws(TypeError, () => format.call(""), "empty string");
+assert.throws(TypeError, () => format.call(Symbol()), "symbol");
+assert.throws(TypeError, () => format.call(1), "1");
+assert.throws(TypeError, () => format.call({}), "plain object");
+assert.throws(TypeError, () => format.call(Intl.RelativeTimeFormat), "Intl.RelativeTimeFormat");
+assert.throws(TypeError, () => format.call(Intl.RelativeTimeFormat.prototype), "Intl.RelativeTimeFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js
new file mode 100644
index 0000000000..2a567d6d01
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js
@@ -0,0 +1,39 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+for (const unit of units) {
+ assert.sameValue(rtf.format(1000, unit), `in 1,000 ${unit}s`);
+ assert.sameValue(rtf.format(10, unit), `in 10 ${unit}s`);
+ assert.sameValue(rtf.format(2, unit), `in 2 ${unit}s`);
+ assert.sameValue(rtf.format(1, unit), `in 1 ${unit}`);
+ assert.sameValue(rtf.format(0, unit), `in 0 ${unit}s`);
+ assert.sameValue(rtf.format(-0, unit), `0 ${unit}s ago`);
+ assert.sameValue(rtf.format(-1, unit), `1 ${unit} ago`);
+ assert.sameValue(rtf.format(-2, unit), `2 ${unit}s ago`);
+ assert.sameValue(rtf.format(-10, unit), `10 ${unit}s ago`);
+ assert.sameValue(rtf.format(-1000, unit), `1,000 ${unit}s ago`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
new file mode 100644
index 0000000000..ef84a32934
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
@@ -0,0 +1,87 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+const rtf = new Intl.RelativeTimeFormat("en-US", { "numeric": "auto" });
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+// https://www.unicode.org/cldr/charts/33/summary/en.html#1530
+const exceptions = {
+ "year": {
+ "-1": "last year",
+ "0": "this year",
+ "1": "next year",
+ },
+ "quarter": {
+ "-1": "last quarter",
+ "0": "this quarter",
+ "1": "next quarter",
+ },
+ "month": {
+ "-1": "last month",
+ "0": "this month",
+ "1": "next month",
+ },
+ "week": {
+ "-1": "last week",
+ "0": "this week",
+ "1": "next week",
+ },
+ "day": {
+ "-1": "yesterday",
+ "0": "today",
+ "1": "tomorrow",
+ },
+ "hour": {
+ "-1": "1 hour ago",
+ '0': 'this hour',
+ "1": "in 1 hour",
+ },
+ "minute": {
+ "-1": "1 minute ago",
+ '0': 'this minute',
+ "1": "in 1 minute",
+ },
+ "second": {
+ "-1": "1 second ago",
+ "0": "now",
+ "1": "in 1 second",
+ },
+};
+
+for (const unit of units) {
+ const expected = unit in exceptions
+ ? [exceptions[unit]["1"], exceptions[unit]["0"], exceptions[unit]["0"], exceptions[unit]["-1"]]
+ : [`in 1 ${unit}`, `in 0 ${unit}s`, `0 ${unit}s ago`, `1 ${unit} ago`];
+
+ assert.sameValue(rtf.format(1000, unit), `in 1,000 ${unit}s`);
+ assert.sameValue(rtf.format(10, unit), `in 10 ${unit}s`);
+ assert.sameValue(rtf.format(2, unit), `in 2 ${unit}s`);
+ assert.sameValue(rtf.format(1, unit), expected[0]);
+ assert.sameValue(rtf.format(0, unit), expected[1]);
+ assert.sameValue(rtf.format(-0, unit), expected[2]);
+ assert.sameValue(rtf.format(-1, unit), expected[3]);
+ assert.sameValue(rtf.format(-2, unit), `2 ${unit}s ago`);
+ assert.sameValue(rtf.format(-10, unit), `10 ${unit}s ago`);
+ assert.sameValue(rtf.format(-1000, unit), `1,000 ${unit}s ago`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js
new file mode 100644
index 0000000000..869ce5036a
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+const units = {
+ "second": ["sec."],
+ "minute": ["min."],
+ "hour": ["hr."],
+ "day": ["day", "days"],
+ "week": ["wk."],
+ "month": ["mo."],
+ "quarter": ["qtr.", "qtrs."],
+ "year": ["yr."],
+};
+
+const rtf = new Intl.RelativeTimeFormat("en-US", {
+ "style": "short",
+});
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+for (const [unitArgument, unitStrings] of Object.entries(units)) {
+ const [singular, plural = singular] = unitStrings;
+ assert.sameValue(rtf.format(1000, unitArgument), `in 1,000 ${plural}`);
+ assert.sameValue(rtf.format(10, unitArgument), `in 10 ${plural}`);
+ assert.sameValue(rtf.format(2, unitArgument), `in 2 ${plural}`);
+ assert.sameValue(rtf.format(1, unitArgument), `in 1 ${singular}`);
+ assert.sameValue(rtf.format(0, unitArgument), `in 0 ${plural}`);
+ assert.sameValue(rtf.format(-0, unitArgument), `0 ${plural} ago`);
+ assert.sameValue(rtf.format(-1, unitArgument), `1 ${singular} ago`);
+ assert.sameValue(rtf.format(-2, unitArgument), `2 ${plural} ago`);
+ assert.sameValue(rtf.format(-10, unitArgument), `10 ${plural} ago`);
+ assert.sameValue(rtf.format(-1000, unitArgument), `1,000 ${plural} ago`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/length.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/length.js
new file mode 100644
index 0000000000..e2f791593b
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/length.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the "length" property of Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The RelativeTimeFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.format, "length", {
+ value: 2,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/name.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/name.js
new file mode 100644
index 0000000000..d71d98d964
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/name.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the "name" property of Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.format, "name", {
+ value: "format",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js
new file mode 100644
index 0000000000..596e1b914c
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js
@@ -0,0 +1,71 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function regular(s) {
+ return {
+ "many": s,
+ "few": s + "y",
+ "one": s + "ę",
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": regular("sekund"),
+ "minute": regular("minut"),
+ "hour": regular("godzin"),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ },
+ "week": {
+ "many": "tygodni",
+ "few": "tygodnie",
+ "one": "tydzień",
+ },
+ "month": {
+ 1000: "miesięcy",
+ "many": "miesięcy",
+ "few": "miesiące",
+ "one": "miesiąc",
+ },
+ "quarter": {
+ "many": "kwartałów",
+ "few": "kwartały",
+ "one": "kwartał",
+ },
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "long",
+});
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ assert.sameValue(rtf.format(1000, unitArgument), `za 1000 ${expected.many}`);
+ assert.sameValue(rtf.format(10, unitArgument), `za 10 ${expected.many}`);
+ assert.sameValue(rtf.format(2, unitArgument), `za 2 ${expected.few}`);
+ assert.sameValue(rtf.format(1, unitArgument), `za 1 ${expected.one}`);
+ assert.sameValue(rtf.format(0, unitArgument), `za 0 ${expected.many}`);
+ assert.sameValue(rtf.format(-0, unitArgument), `0 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1, unitArgument), `1 ${expected.one} temu`);
+ assert.sameValue(rtf.format(-2, unitArgument), `2 ${expected.few} temu`);
+ assert.sameValue(rtf.format(-10, unitArgument), `10 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1000, unitArgument), `1000 ${expected.many} temu`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js
new file mode 100644
index 0000000000..12d32ce1aa
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js
@@ -0,0 +1,62 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function always(s) {
+ return {
+ "many": s,
+ "few": s,
+ "one": s,
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": always("s"),
+ "minute": always("min"),
+ "hour": always("g."),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ },
+ "week": {
+ "many": "tyg.",
+ "few": "tyg.",
+ "one": "tydz.",
+ },
+ "month": always("mies."),
+ "quarter": always("kw."),
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "narrow",
+});
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ assert.sameValue(rtf.format(1000, unitArgument), `za 1000 ${expected.many}`);
+ assert.sameValue(rtf.format(10, unitArgument), `za 10 ${expected.many}`);
+ assert.sameValue(rtf.format(2, unitArgument), `za 2 ${expected.few}`);
+ assert.sameValue(rtf.format(1, unitArgument), `za 1 ${expected.one}`);
+ assert.sameValue(rtf.format(0, unitArgument), `za 0 ${expected.many}`);
+ assert.sameValue(rtf.format(-0, unitArgument), `0 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1, unitArgument), `1 ${expected.one} temu`);
+ assert.sameValue(rtf.format(-2, unitArgument), `2 ${expected.few} temu`);
+ assert.sameValue(rtf.format(-10, unitArgument), `10 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1000, unitArgument), `1000 ${expected.many} temu`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js
new file mode 100644
index 0000000000..27a4a972d8
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js
@@ -0,0 +1,62 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function always(s) {
+ return {
+ "many": s,
+ "few": s,
+ "one": s,
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": always("sek."),
+ "minute": always("min"),
+ "hour": always("godz."),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ },
+ "week": {
+ "many": "tyg.",
+ "few": "tyg.",
+ "one": "tydz.",
+ },
+ "month": always("mies."),
+ "quarter": always("kw."),
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "short",
+});
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ assert.sameValue(rtf.format(1000, unitArgument), `za 1000 ${expected.many}`);
+ assert.sameValue(rtf.format(10, unitArgument), `za 10 ${expected.many}`);
+ assert.sameValue(rtf.format(2, unitArgument), `za 2 ${expected.few}`);
+ assert.sameValue(rtf.format(1, unitArgument), `za 1 ${expected.one}`);
+ assert.sameValue(rtf.format(0, unitArgument), `za 0 ${expected.many}`);
+ assert.sameValue(rtf.format(-0, unitArgument), `0 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1, unitArgument), `1 ${expected.one} temu`);
+ assert.sameValue(rtf.format(-2, unitArgument), `2 ${expected.few} temu`);
+ assert.sameValue(rtf.format(-10, unitArgument), `10 ${expected.many} temu`);
+ assert.sameValue(rtf.format(-1000, unitArgument), `1000 ${expected.many} temu`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/prop-desc.js
new file mode 100644
index 0000000000..0b4b9f8572
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the "format" property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.format ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.RelativeTimeFormat.prototype.format,
+ "function",
+ "typeof Intl.RelativeTimeFormat.prototype.format is function"
+);
+
+verifyProperty(Intl.RelativeTimeFormat.prototype, "format", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-invalid.js
new file mode 100644
index 0000000000..186ff57ec9
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-invalid.js
@@ -0,0 +1,55 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of invalid unit arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ SingularRelativeTimeUnit ( unit )
+
+ 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", "year", throw a RangeError exception.
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function");
+
+const values = [
+ undefined,
+ null,
+ true,
+ 1,
+ 0.1,
+ NaN,
+ {},
+ "",
+ "SECOND",
+ "MINUTE",
+ "HOUR",
+ "DAY",
+ "WEEK",
+ "MONTH",
+ "QUARTER",
+ "YEAR",
+ "decade",
+ "decades",
+ "century",
+ "centuries",
+ "millisecond",
+ "milliseconds",
+ "microsecond",
+ "microseconds",
+ "nanosecond",
+ "nanoseconds",
+];
+
+for (const value of values) {
+ assert.throws(RangeError, () => rtf.format(0, value), String(value));
+}
+
+const symbol = Symbol();
+assert.throws(TypeError, () => rtf.format(0, symbol), "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js
new file mode 100644
index 0000000000..faf8e37ecc
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of plural unit arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ SingularRelativeTimeUnit ( unit )
+
+ 2. If unit is "seconds", return "second".
+ 3. If unit is "minutes", return "minute".
+ 4. If unit is "hours", return "hour".
+ 5. If unit is "days", return "day".
+ 6. If unit is "weeks", return "week".
+ 7. If unit is "months", return "month".
+ 8. If unit is "quarters", return "quarter".
+ 9. If unit is "years", return "year".
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+for (const unit of units) {
+ assert.sameValue(rtf.format(3, unit + "s"), rtf.format(3, unit),
+ `Should support unit ${unit}s as a synonym for ${unit}`)
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js
new file mode 100644
index 0000000000..77827e4c68
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js
@@ -0,0 +1,38 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of invalid value arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Intl.RelativeTimeFormat.prototype.format( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+ PartitionRelativeTimePattern ( relativeTimeFormat, value, unit )
+
+ 4. If isFinite(value) is false, then throw a RangeError exception.
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+const values = [
+ [undefined, "undefined"],
+ [NaN, "NaN"],
+ [Infinity, "Infinity"],
+ [-Infinity, "-Infinity"],
+ ["string", '"string"'],
+ [{}, "empty object"],
+ [{ toString() { return NaN; }, valueOf: undefined }, "object with toString"],
+ [{ valueOf() { return NaN; }, toString: undefined }, "object with valueOf"],
+];
+
+for (const [value, name] of values) {
+ assert.throws(RangeError, () => rtf.format(value, "second"), name);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-symbol.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-symbol.js
new file mode 100644
index 0000000000..83b1617f29
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-symbol.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of invalid value arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Intl.RelativeTimeFormat.prototype.format( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+const symbol = Symbol();
+assert.throws(TypeError, () => rtf.format(symbol, "second"));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-tonumber.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-tonumber.js
new file mode 100644
index 0000000000..2ca5ae6100
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/format/value-tonumber.js
@@ -0,0 +1,40 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of non-number value arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Intl.RelativeTimeFormat.prototype.format( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+const values = [
+ [null, 0],
+
+ [true, 1],
+ [false, 0],
+
+ ["5", 5],
+ ["-5", -5],
+ ["0", 0],
+ ["-0", -0],
+ [" 6 ", 6],
+
+ [{ toString() { return 7; } }, 7, "object with toString"],
+ [{ valueOf() { return 7; } }, 7, "object with valueOf"],
+];
+
+for (const [input, number, name = String(input)] of values) {
+ assert.sameValue(rtf.format(input, "second"), rtf.format(number, "second"),
+ `Should treat ${name} as ${number}`)
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/branding.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/branding.js
new file mode 100644
index 0000000000..fa5f00ad62
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/branding.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Verifies the branding check for the "formatToParts" function of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.formatToParts( value, unit )
+
+ 2. If Type(relativeTimeFormat) is not Object or relativeTimeFormat does not have an [[InitializedRelativeTimeFormat]] internal slot whose value is true, throw a TypeError exception.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const formatToParts = Intl.RelativeTimeFormat.prototype.formatToParts;
+
+assert.sameValue(typeof formatToParts, "function");
+
+assert.throws(TypeError, () => formatToParts.call(undefined), "undefined");
+assert.throws(TypeError, () => formatToParts.call(null), "null");
+assert.throws(TypeError, () => formatToParts.call(true), "true");
+assert.throws(TypeError, () => formatToParts.call(""), "empty string");
+assert.throws(TypeError, () => formatToParts.call(Symbol()), "symbol");
+assert.throws(TypeError, () => formatToParts.call(1), "1");
+assert.throws(TypeError, () => formatToParts.call({}), "plain object");
+assert.throws(TypeError, () => formatToParts.call(Intl.RelativeTimeFormat), "Intl.RelativeTimeFormat");
+assert.throws(TypeError, () => formatToParts.call(Intl.RelativeTimeFormat.prototype), "Intl.RelativeTimeFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-always.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-always.js
new file mode 100644
index 0000000000..b10ee9a701
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-always.js
@@ -0,0 +1,107 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.formatToParts() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const unit of units) {
+ verifyFormatParts(rtf.formatToParts(1000, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "000", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(1000, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "10", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(10, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "2", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(2, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "literal", "value": ` ${unit}` },
+ ], `formatToParts(1, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "0", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(0, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unit), [
+ { "type": "integer", "value": "0", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-0, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unit), [
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "literal", "value": ` ${unit} ago` },
+ ], `formatToParts(-1, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unit), [
+ { "type": "integer", "value": "2", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-2, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unit), [
+ { "type": "integer", "value": "10", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-10, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unit), [
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "000", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-1000, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "123", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "456", "unit": unit },
+ { "type": "decimal", "value": ".", "unit": unit },
+ { "type": "fraction", "value": "78", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(123456.78, ${unit})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js
new file mode 100644
index 0000000000..fdf9ab6487
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js
@@ -0,0 +1,156 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.formatToParts() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+function expected(key, unit, default_) {
+ // https://www.unicode.org/cldr/charts/33/summary/en.html#1530
+ const exceptions = {
+ "year": {
+ "-1": "last year",
+ "0": "this year",
+ "1": "next year",
+ },
+ "quarter": {
+ "-1": "last quarter",
+ "0": "this quarter",
+ "1": "next quarter",
+ },
+ "month": {
+ "-1": "last month",
+ "0": "this month",
+ "1": "next month",
+ },
+ "week": {
+ "-1": "last week",
+ "0": "this week",
+ "1": "next week",
+ },
+ "day": {
+ "-1": "yesterday",
+ "0": "today",
+ "1": "tomorrow",
+ },
+ "hour": {
+ "0": "this hour",
+ },
+ "minute": {
+ "0": "this minute",
+ },
+ "second": {
+ "0": "now",
+ },
+ };
+
+ const exception = exceptions[unit] || {};
+ if (key in exception) {
+ return [
+ { "type": "literal", "value": exception[key] },
+ ]
+ }
+
+ return default_;
+}
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+const rtf = new Intl.RelativeTimeFormat("en-US", { "numeric": "auto" });
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const unit of units) {
+ verifyFormatParts(rtf.formatToParts(1000, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "000", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(1000, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "10", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(10, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "2", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(2, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unit), expected("1", unit, [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "literal", "value": ` ${unit}` },
+ ]), `formatToParts(1, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unit), expected("0", unit, [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "0", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ]), `formatToParts(0, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unit), expected("0", unit, [
+ { "type": "integer", "value": "0", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ]), `formatToParts(-0, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unit), expected("-1", unit, [
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "literal", "value": ` ${unit} ago` },
+ ]), `formatToParts(-1, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unit), [
+ { "type": "integer", "value": "2", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-2, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unit), [
+ { "type": "integer", "value": "10", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-10, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unit), [
+ { "type": "integer", "value": "1", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "000", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s ago` },
+ ], `formatToParts(-1000, ${unit})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unit), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "123", "unit": unit },
+ { "type": "group", "value": ",", "unit": unit },
+ { "type": "integer", "value": "456", "unit": unit },
+ { "type": "decimal", "value": ".", "unit": unit },
+ { "type": "fraction", "value": "78", "unit": unit },
+ { "type": "literal", "value": ` ${unit}s` },
+ ], `formatToParts(123456.78, ${unit})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-style-short.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-style-short.js
new file mode 100644
index 0000000000..812a66d8c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-style-short.js
@@ -0,0 +1,111 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.formatToParts() in English.
+features: [Intl.RelativeTimeFormat]
+locale: [en-US]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+const units = {
+ "second": ["sec."],
+ "minute": ["min."],
+ "hour": ["hr."],
+ "day": ["day", "days"],
+ "week": ["wk."],
+ "month": ["mo."],
+ "quarter": ["qtr.", "qtrs."],
+ "year": ["yr."],
+};
+
+const rtf = new Intl.RelativeTimeFormat("en-US", {
+ "style": "short",
+});
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const [unitArgument, unitStrings] of Object.entries(units)) {
+ const [singular, plural = singular] = unitStrings;
+
+ verifyFormatParts(rtf.formatToParts(1000, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "group", "value": ",", "unit": unitArgument },
+ { "type": "integer", "value": "000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural}` },
+ ], `formatToParts(1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural}` },
+ ], `formatToParts(10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural}` },
+ ], `formatToParts(2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${singular}` },
+ ], `formatToParts(1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural}` },
+ ], `formatToParts(0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unitArgument), [
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural} ago` },
+ ], `formatToParts(-0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unitArgument), [
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${singular} ago` },
+ ], `formatToParts(-1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unitArgument), [
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural} ago` },
+ ], `formatToParts(-2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unitArgument), [
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural} ago` },
+ ], `formatToParts(-10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unitArgument), [
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "group", "value": ",", "unit": unitArgument },
+ { "type": "integer", "value": "000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural} ago` },
+ ], `formatToParts(-1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unitArgument), [
+ { "type": "literal", "value": "in " },
+ { "type": "integer", "value": "123", "unit": unitArgument },
+ { "type": "group", "value": ",", "unit": unitArgument },
+ { "type": "integer", "value": "456", "unit": unitArgument },
+ { "type": "decimal", "value": ".", "unit": unitArgument },
+ { "type": "fraction", "value": "78", "unit": unitArgument },
+ { "type": "literal", "value": ` ${plural}` },
+ ], `formatToParts(123456.78, ${unitArgument})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/length.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/length.js
new file mode 100644
index 0000000000..c6badcd71a
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/length.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the "length" property of Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The RelativeTimeFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.formatToParts, "length", {
+ value: 2,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/name.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/name.js
new file mode 100644
index 0000000000..df10bfe3a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/name.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the "name" property of Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.formatToParts, "name", {
+ value: "formatToParts",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-long.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-long.js
new file mode 100644
index 0000000000..0a10fe68e6
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-long.js
@@ -0,0 +1,141 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+function regular(s) {
+ return {
+ "many": s,
+ "few": s + "y",
+ "one": s + "ę",
+ "other": s + "y",
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": regular("sekund"),
+ "minute": regular("minut"),
+ "hour": regular("godzin"),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ "other": "dnia",
+ },
+ "week": {
+ "many": "tygodni",
+ "few": "tygodnie",
+ "one": "tydzień",
+ "other": "tygodnia",
+ },
+ "month": {
+ 1000: "miesięcy",
+ "many": "miesięcy",
+ "few": "miesiące",
+ "one": "miesiąc",
+ "other": "miesiąca",
+ },
+ "quarter": {
+ "many": "kwartałów",
+ "few": "kwartały",
+ "one": "kwartał",
+ "other": "kwartału",
+ },
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ "other": "roku",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "long",
+});
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ verifyFormatParts(rtf.formatToParts(1000, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few}` },
+ ], `formatToParts(2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one}` },
+ ], `formatToParts(1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unitArgument), [
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unitArgument), [
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one} temu` },
+ ], `formatToParts(-1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unitArgument), [
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few} temu` },
+ ], `formatToParts(-2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unitArgument), [
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unitArgument), [
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "123", "unit": unitArgument },
+ { "type": "group", "value": "\u00a0", "unit": unitArgument },
+ { "type": "integer", "value": "456", "unit": unitArgument },
+ { "type": "decimal", "value": ",", "unit": unitArgument },
+ { "type": "fraction", "value": "78", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.other}` },
+ ], `formatToParts(123456.78, ${unitArgument})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-narrow.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-narrow.js
new file mode 100644
index 0000000000..f8af023ca2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-narrow.js
@@ -0,0 +1,130 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+function always(s) {
+ return {
+ "many": s,
+ "few": s,
+ "one": s,
+ "other": s,
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": always("s"),
+ "minute": always("min"),
+ "hour": always("g."),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ "other": "dnia",
+ },
+ "week": {
+ "many": "tyg.",
+ "few": "tyg.",
+ "one": "tydz.",
+ "other": "tyg.",
+ },
+ "month": always("mies."),
+ "quarter": always("kw."),
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ "other": "roku",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "narrow",
+});
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ verifyFormatParts(rtf.formatToParts(1000, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few}` },
+ ], `formatToParts(2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one}` },
+ ], `formatToParts(1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unitArgument), [
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unitArgument), [
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one} temu` },
+ ], `formatToParts(-1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unitArgument), [
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few} temu` },
+ ], `formatToParts(-2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unitArgument), [
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unitArgument), [
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "123", "unit": unitArgument },
+ { "type": "group", "value": "\u00a0", "unit": unitArgument },
+ { "type": "integer", "value": "456", "unit": unitArgument },
+ { "type": "decimal", "value": ",", "unit": unitArgument },
+ { "type": "fraction", "value": "78", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.other}` },
+ ], `formatToParts(123456.78, ${unitArgument})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-short.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-short.js
new file mode 100644
index 0000000000..97734b1365
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-short.js
@@ -0,0 +1,130 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the behavior of Intl.RelativeTimeFormat.prototype.format() in Polish.
+features: [Intl.RelativeTimeFormat]
+locale: [pl-PL]
+---*/
+
+function verifyFormatParts(actual, expected, message) {
+ assert.sameValue(actual.length, expected.length, `${message}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type, `${message}: parts[${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value, `${message}: parts[${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit, `${message}: parts[${i}].unit`);
+ }
+}
+
+function always(s) {
+ return {
+ "many": s,
+ "few": s,
+ "one": s,
+ "other": s,
+ }
+}
+
+// https://www.unicode.org/cldr/charts/33/summary/pl.html#1419
+const units = {
+ "second": always("sek."),
+ "minute": always("min"),
+ "hour": always("godz."),
+ "day": {
+ "many": "dni",
+ "few": "dni",
+ "one": "dzień",
+ "other": "dnia",
+ },
+ "week": {
+ "many": "tyg.",
+ "few": "tyg.",
+ "one": "tydz.",
+ "other": "tyg.",
+ },
+ "month": always("mies."),
+ "quarter": always("kw."),
+ "year": {
+ "many": "lat",
+ "few": "lata",
+ "one": "rok",
+ "other": "roku",
+ },
+};
+
+const rtf = new Intl.RelativeTimeFormat("pl-PL", {
+ "style": "short",
+});
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+for (const [unitArgument, expected] of Object.entries(units)) {
+ verifyFormatParts(rtf.formatToParts(1000, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(10, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(2, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few}` },
+ ], `formatToParts(2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(1, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one}` },
+ ], `formatToParts(1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(0, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many}` },
+ ], `formatToParts(0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-0, unitArgument), [
+ { "type": "integer", "value": "0", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-0, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1, unitArgument), [
+ { "type": "integer", "value": "1", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.one} temu` },
+ ], `formatToParts(-1, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-2, unitArgument), [
+ { "type": "integer", "value": "2", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.few} temu` },
+ ], `formatToParts(-2, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-10, unitArgument), [
+ { "type": "integer", "value": "10", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-10, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(-1000, unitArgument), [
+ { "type": "integer", "value": "1000", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.many} temu` },
+ ], `formatToParts(-1000, ${unitArgument})`);
+
+ verifyFormatParts(rtf.formatToParts(123456.78, unitArgument), [
+ { "type": "literal", "value": "za " },
+ { "type": "integer", "value": "123", "unit": unitArgument },
+ { "type": "group", "value": "\u00a0", "unit": unitArgument },
+ { "type": "integer", "value": "456", "unit": unitArgument },
+ { "type": "decimal", "value": ",", "unit": unitArgument },
+ { "type": "fraction", "value": "78", "unit": unitArgument },
+ { "type": "literal", "value": ` ${expected.other}` },
+ ], `formatToParts(123456.78, ${unitArgument})`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/prop-desc.js
new file mode 100644
index 0000000000..257c9ea533
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the "formatToParts" property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.formatToParts ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.RelativeTimeFormat.prototype.formatToParts,
+ "function",
+ "typeof Intl.RelativeTimeFormat.prototype.formatToParts is function"
+);
+
+verifyProperty(Intl.RelativeTimeFormat.prototype, "formatToParts", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/result-type.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/result-type.js
new file mode 100644
index 0000000000..127642a5ef
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/result-type.js
@@ -0,0 +1,84 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the handling of plural unit arguments to Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ FormatRelativeTimeToParts ( relativeTimeFormat, value, unit )
+
+ 3. Let n be 0.
+ 4. For each part in parts, do:
+ a. Let O be ObjectCreate(%ObjectPrototype%).
+ b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
+ c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
+ d. If part has a [[Unit]] field,
+ i. Perform ! CreateDataPropertyOrThrow(O, "unit", part.[[Unit]]).
+ e. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
+ f. Increment n by 1.
+
+features: [Intl.RelativeTimeFormat]
+includes: [propertyHelper.js]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+const parts = rtf.formatToParts(3, "second");
+
+assert.sameValue(Object.getPrototypeOf(parts), Array.prototype, "parts: prototype");
+assert.sameValue(Array.isArray(parts), true, "parts: isArray");
+assert.sameValue(parts.length, 3, "parts: length");
+
+assert.sameValue(Object.getPrototypeOf(parts[0]), Object.prototype, "parts[0]: prototype");
+verifyProperty(parts[0], "type", {
+ value: "literal",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+verifyProperty(parts[0], "value", {
+ value: "in ",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+
+assert.sameValue(Object.getPrototypeOf(parts[1]), Object.prototype, "parts[1]: prototype");
+verifyProperty(parts[1], "type", {
+ value: "integer",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+verifyProperty(parts[1], "value", {
+ value: "3",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+verifyProperty(parts[1], "unit", {
+ value: "second",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+
+assert.sameValue(Object.getPrototypeOf(parts[2]), Object.prototype, "parts[2]: prototype");
+verifyProperty(parts[2], "type", {
+ value: "literal",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+verifyProperty(parts[2], "value", {
+ value: " seconds",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-invalid.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-invalid.js
new file mode 100644
index 0000000000..94e4355e09
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-invalid.js
@@ -0,0 +1,55 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the handling of invalid unit arguments to Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ SingularRelativeTimeUnit ( unit )
+
+ 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", "year", throw a RangeError exception.
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function");
+
+const values = [
+ undefined,
+ null,
+ true,
+ 1,
+ 0.1,
+ NaN,
+ {},
+ "",
+ "SECOND",
+ "MINUTE",
+ "HOUR",
+ "DAY",
+ "WEEK",
+ "MONTH",
+ "QUARTER",
+ "YEAR",
+ "decade",
+ "decades",
+ "century",
+ "centuries",
+ "millisecond",
+ "milliseconds",
+ "microsecond",
+ "microseconds",
+ "nanosecond",
+ "nanoseconds",
+];
+
+for (const value of values) {
+ assert.throws(RangeError, () => rtf.formatToParts(0, value), String(value));
+}
+
+const symbol = Symbol();
+assert.throws(TypeError, () => rtf.formatToParts(0, symbol), "symbol");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-plural.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-plural.js
new file mode 100644
index 0000000000..210ce7c8b4
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/unit-plural.js
@@ -0,0 +1,54 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the handling of plural unit arguments to Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ SingularRelativeTimeUnit ( unit )
+
+ 2. If unit is "seconds", return "second".
+ 3. If unit is "minutes", return "minute".
+ 4. If unit is "hours", return "hour".
+ 5. If unit is "days", return "day".
+ 6. If unit is "weeks", return "week".
+ 7. If unit is "months", return "month".
+ 8. If unit is "quarters", return "quarter".
+ 9. If unit is "years", return "year".
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+const units = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "quarter",
+ "year",
+];
+
+for (const unit of units) {
+ const plural = rtf.formatToParts(3, unit + "s");
+ const singular = rtf.formatToParts(3, unit);
+
+ assert.sameValue(plural.length, singular.length,
+ `Should support unit ${unit}s as a synonym for ${unit}: length`);
+
+ for (let i = 0; i < plural.length; ++i) {
+ assert.sameValue(plural[i].type, singular[i].type,
+ `Should support unit ${unit}s as a synonym for ${unit}: [${i}].type`);
+ assert.sameValue(plural[i].value, singular[i].value,
+ `Should support unit ${unit}s as a synonym for ${unit}: [${i}].value`);
+ assert.sameValue(plural[i].unit, singular[i].unit,
+ `Should support unit ${unit}s as a synonym for ${unit}: [${i}].unit`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-non-finite.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-non-finite.js
new file mode 100644
index 0000000000..f0c888d165
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-non-finite.js
@@ -0,0 +1,38 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the handling of invalid value arguments to Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ Intl.RelativeTimeFormat.prototype.formatToParts( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+ PartitionRelativeTimePattern ( relativeTimeFormat, value, unit )
+
+ 4. If isFinite(value) is false, then throw a RangeError exception.
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+const values = [
+ [undefined, "undefined"],
+ [NaN, "NaN"],
+ [Infinity, "Infinity"],
+ [-Infinity, "-Infinity"],
+ ["string", '"string"'],
+ [{}, "empty object"],
+ [{ toString() { return NaN; }, valueOf: undefined }, "object with toString"],
+ [{ valueOf() { return NaN; }, toString: undefined }, "object with valueOf"],
+];
+
+for (const [value, name] of values) {
+ assert.throws(RangeError, () => rtf.formatToParts(value, "second"), name);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-symbol.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-symbol.js
new file mode 100644
index 0000000000..6592cf3fa1
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-symbol.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.formatToParts
+description: Checks the handling of invalid value arguments to Intl.RelativeTimeFormat.prototype.formatToParts().
+info: |
+ Intl.RelativeTimeFormat.prototype.formatToParts( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.formatToParts, "function", "formatToParts should be supported");
+
+const symbol = Symbol();
+assert.throws(TypeError, () => rtf.formatToParts(symbol, "second"));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-tonumber.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-tonumber.js
new file mode 100644
index 0000000000..7fc4c733a0
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/formatToParts/value-tonumber.js
@@ -0,0 +1,52 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.format
+description: Checks the handling of non-number value arguments to Intl.RelativeTimeFormat.prototype.format().
+info: |
+ Intl.RelativeTimeFormat.prototype.format( value, unit )
+
+ 3. Let value be ? ToNumber(value).
+
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-US");
+
+assert.sameValue(typeof rtf.format, "function", "format should be supported");
+
+const values = [
+ [null, 0],
+
+ [true, 1],
+ [false, 0],
+
+ ["5", 5],
+ ["-5", -5],
+ ["0", 0],
+ ["-0", -0],
+ [" 6 ", 6],
+
+ [{ toString() { return 7; } }, 7, "object with toString"],
+ [{ valueOf() { return 7; } }, 7, "object with valueOf"],
+];
+
+for (const [input, number, name = String(input)] of values) {
+ const actual = rtf.formatToParts(input, "second");
+ const expected = rtf.formatToParts(number, "second");
+
+ assert.sameValue(actual.length, expected.length,
+ `Should treat ${name} as ${number}: length`);
+
+ for (let i = 0; i < actual.length; ++i) {
+ assert.sameValue(actual[i].type, expected[i].type,
+ `Should treat ${name} as ${number}: [${i}].type`);
+ assert.sameValue(actual[i].value, expected[i].value,
+ `Should treat ${name} as ${number}: [${i}].value`);
+ assert.sameValue(actual[i].unit, expected[i].unit,
+ `Should treat ${name} as ${number}: [${i}].unit`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/prop-desc.js
new file mode 100644
index 0000000000..c9aa6913c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/prop-desc.js
@@ -0,0 +1,24 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype
+description: >
+ Checks the "prototype" property of the RelativeTimeFormat constructor.
+info: |
+ Intl.RelativeTimeFormat.prototype
+
+ The value of Intl.RelativeTimeFormat.prototype is %RelativeTimeFormatPrototype%.
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/branding.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/branding.js
new file mode 100644
index 0000000000..ba3ab34bee
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/branding.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Verifies the branding check for the "resolvedOptions" function of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.resolvedOptions ()
+
+ 2. If Type(relativeTimeFormat) is not Object or relativeTimeFormat does not have an [[InitializedRelativeTimeFormat]] internal slot whose value is true, throw a TypeError exception.
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const resolvedOptions = Intl.RelativeTimeFormat.prototype.resolvedOptions;
+
+assert.sameValue(typeof resolvedOptions, "function");
+
+assert.throws(TypeError, () => resolvedOptions.call(undefined), "undefined");
+assert.throws(TypeError, () => resolvedOptions.call(null), "null");
+assert.throws(TypeError, () => resolvedOptions.call(true), "true");
+assert.throws(TypeError, () => resolvedOptions.call(""), "empty string");
+assert.throws(TypeError, () => resolvedOptions.call(Symbol()), "symbol");
+assert.throws(TypeError, () => resolvedOptions.call(1), "1");
+assert.throws(TypeError, () => resolvedOptions.call({}), "plain object");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.RelativeTimeFormat), "Intl.RelativeTimeFormat");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.RelativeTimeFormat.prototype), "Intl.RelativeTimeFormat.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/caching.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/caching.js
new file mode 100644
index 0000000000..c00142c482
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/caching.js
@@ -0,0 +1,19 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Verifies that the return value of Intl.RelativeTimeFormat.prototype.resolvedOptions() is not cached.
+info: |
+ Intl.RelativeTimeFormat.prototype.resolvedOptions ()
+
+ 4. Let options be ! ObjectCreate(%ObjectPrototype%).
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-us");
+const options1 = rtf.resolvedOptions();
+const options2 = rtf.resolvedOptions();
+assert.notSameValue(options1, options2, "Should create a new object each time.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..8c49fd2e98
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/length.js
@@ -0,0 +1,23 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Checks the "length" property of Intl.RelativeTimeFormat.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The RelativeTimeFormat constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..2305662ed6
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/name.js
@@ -0,0 +1,22 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Checks the "name" property of Intl.RelativeTimeFormat.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..c0017b721c
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/order.js
@@ -0,0 +1,29 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.relativetimeformat.prototype.resolvedoptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const options = new Intl.RelativeTimeFormat().resolvedOptions();
+
+const expected = [
+ "locale",
+ "style",
+ "numeric",
+ "numberingSystem",
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..cdb51d7ddd
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,30 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Checks the "resolvedOptions" property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype.resolvedOptions ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(
+ typeof Intl.RelativeTimeFormat.prototype.resolvedOptions,
+ "function",
+ "typeof Intl.RelativeTimeFormat.prototype.resolvedOptions is function"
+);
+
+verifyProperty(Intl.RelativeTimeFormat.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/type.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/type.js
new file mode 100644
index 0000000000..4ad150eb48
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/resolvedOptions/type.js
@@ -0,0 +1,42 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.RelativeTimeFormat.prototype.resolvedOptions
+description: Checks the properties of the result of Intl.RelativeTimeFormat.prototype.resolvedOptions().
+info: |
+ Intl.RelativeTimeFormat.prototype.resolvedOptions ()
+
+ 4. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 5. For each row of Table 1, except the header row, do
+ d. Perform ! CreateDataPropertyOrThrow(options, p, v).
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat]
+---*/
+
+const rtf = new Intl.RelativeTimeFormat("en-us", { "style": "short", "numeric": "auto" });
+const options = rtf.resolvedOptions();
+assert.sameValue(Object.getPrototypeOf(options), Object.prototype, "Prototype");
+
+verifyProperty(options, "locale", {
+ value: "en-US",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "style", {
+ value: "short",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "numeric", {
+ value: "auto",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..100584e18c
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toString.js
@@ -0,0 +1,18 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.RelativeTimeFormat.prototype-@@tostringtag
+description: >
+ Checks Object.prototype.toString with Intl.RelativeTimeFormat objects.
+info: |
+ Intl.RelativeTimeFormat.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.RelativeTimeFormat".
+features: [Intl.RelativeTimeFormat]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.RelativeTimeFormat.prototype), "[object Intl.RelativeTimeFormat]");
+assert.sameValue(Object.prototype.toString.call(new Intl.RelativeTimeFormat("en")), "[object Intl.RelativeTimeFormat]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..80398ba2d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,25 @@
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.RelativeTimeFormat.prototype-@@tostringtag
+description: >
+ Checks the @@toStringTag property of the RelativeTimeFormat prototype object.
+info: |
+ Intl.RelativeTimeFormat.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.RelativeTimeFormat".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.RelativeTimeFormat, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.RelativeTimeFormat.prototype, Symbol.toStringTag, {
+ value: "Intl.RelativeTimeFormat",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/RelativeTimeFormat/shell.js b/js/src/tests/test262/intl402/RelativeTimeFormat/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/RelativeTimeFormat/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/browser.js b/js/src/tests/test262/intl402/Segmenter/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/browser.js b/js/src/tests/test262/intl402/Segmenter/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/browser.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-invalid.js
new file mode 100644
index 0000000000..cb4b709c05
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-invalid.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks error cases for the locales argument to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+includes: [testIntl.js]
+features: [Intl.Segmenter]
+---*/
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, function() { new Intl.Segmenter(locales) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-valid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-valid.js
new file mode 100644
index 0000000000..3aa9976448
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/locales-valid.js
@@ -0,0 +1,53 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks various cases for the locales argument to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 3. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_).
+features: [Intl.Segmenter, Array.prototype.includes]
+---*/
+
+const defaultLocale = new Intl.Segmenter().resolvedOptions().locale;
+
+const tests = [
+ [undefined, [defaultLocale], "undefined"],
+ ["EN", ["en"], "Single value"],
+ [[], [defaultLocale], "Empty array"],
+ [["sr"], ["sr"], "Single-element array"],
+ [["fr", "ar"], ["fr", "ar"], "Two-element array"],
+ [["xyz", "ar"], ["ar"], "Two-element array with unknown code"],
+ [["en", "EN"], ["en"], "Duplicate value (canonical first)"],
+ [["EN", "en"], ["en"], "Duplicate value (canonical last)"],
+ [{ 0: "DE", length: 0 }, [defaultLocale], "Object with zero length"],
+ [{ 0: "DE", length: 1 }, ["de"], "Object with length"],
+ [{ 0: "ja", 1: "fr" }, [defaultLocale], "Object without length, indexed from 0"],
+ [{ 1: "ja", 2: "fr" }, [defaultLocale], "Object without length, indexed from 1"],
+];
+
+const errorTests = [
+ [["en-GB-oed"], "Grandfathered"],
+ [["x-private"], "Private", ["lookup"]],
+];
+
+for (const [locales, expected, name, matchers = ["best fit", "lookup"]] of tests) {
+ for (const localeMatcher of matchers) {
+ const segmenter = new Intl.Segmenter(locales, { localeMatcher });
+ const actual = segmenter.resolvedOptions().locale;
+ assert(expected.includes(actual), `${name}: expected one of ${expected}, found ${actual}`);
+ }
+}
+
+for (const [locales, name, matchers = ["best fit", "lookup"]] of errorTests) {
+ for (const localeMatcher of matchers) {
+ assert.throws(RangeError, function() {
+ new Intl.Segmenter(locales, { localeMatcher });
+ }, name);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/newtarget-undefined.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/newtarget-undefined.js
new file mode 100644
index 0000000000..37d4d35d2b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/newtarget-undefined.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Verifies the NewTarget check for Intl.Segmenter.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter, "function");
+
+assert.throws(TypeError, function() {
+ Intl.Segmenter();
+});
+
+assert.throws(TypeError, function() {
+ Intl.Segmenter("en");
+});
+
+assert.throws(TypeError, function() {
+ Intl.Segmenter("not-valid-tag");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-getoptionsobject.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-getoptionsobject.js
new file mode 100644
index 0000000000..c8a1851ae3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-getoptionsobject.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of non-object option arguments to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+features: [Intl.Segmenter,BigInt]
+---*/
+
+const optionsArguments = [
+ null,
+ true,
+ false,
+ "test",
+ 7,
+ Symbol(),
+ 123456789n,
+];
+
+for (const options of optionsArguments) {
+ assert.throws(TypeError, function() { new Intl.Segment([], options) })
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-abrupt-throws.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-abrupt-throws.js
new file mode 100644
index 0000000000..ed64988cfd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-abrupt-throws.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: Return abrupt completion from GetOption granularity
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 13. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+
+ GetOption ( options, property, type, values, fallback )
+ 1. Let value be ? Get(options, property).
+features: [Intl.Segmenter]
+---*/
+
+
+var options = {};
+Object.defineProperty(options, 'granularity', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-invalid.js
new file mode 100644
index 0000000000..37f0fa262d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-invalid.js
@@ -0,0 +1,42 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of invalid value for the style option to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 13. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+ 14. Set segmenter.[[SegmenterGranularity]] to granularity.
+features: [Intl.Segmenter]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "standard",
+ "Grapheme",
+ "GRAPHEME",
+ "grapheme\0",
+ "Word",
+ "WORD",
+ "word\0",
+ "Sentence",
+ "SENTENCE",
+ "sentence\0",
+ "line",
+ "Line",
+ "LINE",
+ "line\0",
+];
+
+for (const granularity of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Segmenter([], { granularity });
+ }, `${granularity} is an invalid style option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-toString-abrupt-throws.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-toString-abrupt-throws.js
new file mode 100644
index 0000000000..547dd69381
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-toString-abrupt-throws.js
@@ -0,0 +1,59 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: Return abrupt completion from GetOption granularity
+
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 13. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+
+ GetOption ( options, property, type, values, fallback )
+ 6. If type is "string", then
+ a. Let value be ? ToString(value).
+features: [Intl.Segmenter, Symbol]
+---*/
+
+const options = {
+ granularity: {
+ toString() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from toString');
+
+options.granularity = {
+ toString: undefined,
+ valueOf() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from valueOf');
+
+options.granularity = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from ToPrimitive');
+
+options.granularity = Symbol();
+
+assert.throws(TypeError, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-valid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-valid.js
new file mode 100644
index 0000000000..f96b1cf45a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-granularity-valid.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of valid values for the granularity option to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 11. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+ 12. Set segmenter.[[SegmenterGranularity]] to granularity.
+features: [Intl.Segmenter]
+---*/
+
+const validOptions = [
+ [undefined, "grapheme"],
+ ["grapheme", "grapheme"],
+ ["word", "word"],
+ ["sentence", "sentence"],
+ [{ toString() { return "word"; } }, "word"],
+];
+
+for (const [granularity, expected] of validOptions) {
+ const segmenter = new Intl.Segmenter([], { granularity });
+ const resolvedOptions = segmenter.resolvedOptions();
+ assert.sameValue(resolvedOptions.granularity, expected);
+}
+
+assert.throws(RangeError, () => new Intl.Segmenter([], {granularity: "line"}));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-invalid.js
new file mode 100644
index 0000000000..107efa8c9b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-invalid.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of a null options argument to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 5. Else
+ a. Let options be ? ToObject(options).
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter, "function");
+
+assert.throws(TypeError, function() { new Intl.Segmenter([], null) })
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-abrupt-throws.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-abrupt-throws.js
new file mode 100644
index 0000000000..16548dcc13
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-abrupt-throws.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Return abrupt completion from GetOption localeMatcher
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ GetOption ( options, property, type, values, fallback )
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.Segmenter]
+---*/
+
+var options = {};
+Object.defineProperty(options, 'localeMatcher', {
+ get() { throw new Test262Error(); },
+});
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..53fe25e8a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-invalid.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of invalid value for the localeMatcher option to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.Segmenter]
+---*/
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const localeMatcher of invalidOptions) {
+ assert.throws(RangeError, function() {
+ new Intl.Segmenter([], { localeMatcher });
+ }, `${localeMatcher} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-toString-abrupt-throws.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-toString-abrupt-throws.js
new file mode 100644
index 0000000000..991af934db
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-toString-abrupt-throws.js
@@ -0,0 +1,68 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Return abrupt completion from GetOption localeMatcher
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+
+ GetOption ( options, property, type, values, fallback )
+ 6. If type is "string", then
+ a. Let value be ? ToString(value).
+ ...
+features: [Intl.Segmenter, Symbol]
+---*/
+
+const options = {
+ localeMatcher: {
+ toString() {
+ throw new Test262Error();
+ }
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from toString');
+
+options.localeMatcher = {
+ toString: undefined,
+ valueOf() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from valueOf');
+
+options.localeMatcher = {
+ [Symbol.toPrimitive]() {
+ throw new Test262Error();
+ }
+};
+
+assert.throws(Test262Error, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'from ToPrimitive');
+
+options.localeMatcher = Symbol();
+
+assert.throws(TypeError, () => {
+ new Intl.Segmenter(undefined, options);
+}, 'symbol value');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-valid.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-valid.js
new file mode 100644
index 0000000000..e572cf14b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-localeMatcher-valid.js
@@ -0,0 +1,41 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Valid options for localeMatcher
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ ...
+
+ GetOption ( options, property, type, values, fallback )
+
+ 1. Let value be ? Get(options, property).
+ ...
+features: [Intl.Segmenter]
+locale: [en]
+---*/
+
+// results for option values verified in the tests for resolvedOptions
+
+const localeMatchers = [
+ undefined,
+ 'lookup',
+ 'best fit'
+];
+
+localeMatchers.forEach(localeMatcher => {
+ const obj = new Intl.Segmenter(undefined, { localeMatcher });
+
+ assert(obj instanceof Intl.Segmenter, `instanceof check - ${localeMatcher}`);
+ assert.sameValue(Object.getPrototypeOf(obj), Intl.Segmenter.prototype, `proto check - ${localeMatcher}`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-null.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-null.js
new file mode 100644
index 0000000000..f3f9d56c31
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-null.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.Segmenter
+description: >
+ Throws TypeError if options is null
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+ 5. Else
+ a. Let options be ? ToObject(options).
+ ...
+features: [Intl.Segmenter]
+---*/
+
+assert.throws(TypeError, () => {
+ new Intl.Segmenter(undefined, null);
+}, 'null');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-order.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-order.js
new file mode 100644
index 0000000000..aaf1464569
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-order.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks the order of operations on the options argument to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 7. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
+ 11. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+const callOrder = [];
+
+new Intl.Segmenter([], {
+ get localeMatcher() {
+ callOrder.push("localeMatcher");
+ return {
+ toString() {
+ callOrder.push("localeMatcher toString");
+ return "best fit";
+ }
+ };
+ },
+ get lineBreakStyle() {
+ callOrder.push("lineBreakStyle");
+ return {
+ toString() {
+ callOrder.push("lineBreakStyle toString");
+ return "strict";
+ }
+ };
+ },
+ get granularity() {
+ callOrder.push("granularity");
+ return {
+ toString() {
+ callOrder.push("granularity toString");
+ return "word";
+ }
+ };
+ },
+});
+
+assert.compareArray(callOrder, [
+ "localeMatcher",
+ "localeMatcher toString",
+ "granularity",
+ "granularity toString",
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-throwing-getters.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-throwing-getters.js
new file mode 100644
index 0000000000..4329cfedcd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-throwing-getters.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks the propagation of exceptions from the options for the Segmenter constructor.
+features: [Intl.Segmenter]
+---*/
+
+function CustomError() {}
+
+const options = [
+ "localeMatcher",
+ "granularity",
+];
+
+for (const option of options) {
+ assert.throws(CustomError, () => {
+ new Intl.Segmenter("en", {
+ get [option]() {
+ throw new CustomError();
+ }
+ });
+ }, `Exception from ${option} getter should be propagated`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-undefined.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-undefined.js
new file mode 100644
index 0000000000..76486524f9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-undefined.js
@@ -0,0 +1,52 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of non-object option arguments to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 4. If options is undefined, then
+ a. Let options be ObjectCreate(null).
+features: [Intl.Segmenter]
+---*/
+
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ "get": function() {
+ throw new Error("Should not call getter on Object.prototype: localeMatcher");
+ },
+ },
+
+ "lineBreakStyle": {
+ "get": function() {
+ throw new Error("Should not call getter on Object.prototype: lineBreakStyle");
+ },
+ },
+
+ "granularity": {
+ "get": function() {
+ throw new Error("Should not call getter on Object.prototype: granularity");
+ },
+ },
+});
+
+const optionsArguments = [
+ [],
+ [[]],
+ [[], undefined],
+];
+
+for (const args of optionsArguments) {
+ const segmenter = new Intl.Segmenter(...args);
+ const resolvedOptions = segmenter.resolvedOptions();
+ assert.sameValue(resolvedOptions.granularity, "grapheme",
+ `Calling with ${args.length} empty arguments should yield the correct value for "granularity"`);
+ assert(
+ !Object.prototype.hasOwnProperty.call(resolvedOptions, "lineBreakStyle"),
+ `Calling with ${args.length} empty arguments should yield the correct value for "lineBreakStyle"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-valid-combinations.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-valid-combinations.js
new file mode 100644
index 0000000000..dfddc80cee
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/options-valid-combinations.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks handling of valid values for the granularity option to the Segmenter constructor.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 11. Let granularity be ? GetOption(options, "granularity", "string", « "grapheme", "word", "sentence" », "grapheme").
+ 12. Set segmenter.[[SegmenterGranularity]] to granularity.
+features: [Intl.Segmenter]
+---*/
+
+const granularityOptions = ["grapheme", "word", "sentence"];
+const combinations = [];
+
+combinations.push([
+ {},
+ "grapheme",
+ undefined,
+]);
+
+for (const granularity of granularityOptions) {
+ combinations.push([
+ { granularity },
+ granularity,
+ undefined,
+ ]);
+}
+
+for (const [input, granularity, lineBreakStyle] of combinations) {
+ const segmenter = new Intl.Segmenter([], input);
+ const resolvedOptions = segmenter.resolvedOptions();
+ assert.sameValue(resolvedOptions.granularity, granularity);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..e3bc3b7be1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/proto-from-ctor-realm.js
@@ -0,0 +1,61 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 2. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%SegmenterPrototype%", « [[InitializedSegmenter]] »).
+ ...
+ 14. Return segmenter.
+
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.Segmenter, cross-realm, Reflect, Symbol]
+---*/
+
+var other = $262.createRealm().global;
+var newTarget = new other.Function();
+var seg;
+
+newTarget.prototype = undefined;
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = false;
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 1;
+seg = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(seg), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/shell.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/constructor/subclassing.js b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/subclassing.js
new file mode 100644
index 0000000000..485ece668b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/constructor/subclassing.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks that Segmenter can be subclassed.
+info: |
+ Intl.Segmenter ( [ locales [ , options ] ] )
+
+ 2. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%SegmenterPrototype%", « [[InitializedSegmenter]] »).
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+function segments(iterator) {
+ return [...iterator].map(result => result.segment);
+}
+
+class CustomSegmenter extends Intl.Segmenter {
+ constructor(locales, options) {
+ super(locales, options);
+ this.isCustom = true;
+ }
+}
+
+const locale = "de";
+const value = "Hello";
+
+const real_segmenter = new Intl.Segmenter(locale);
+assert.sameValue(real_segmenter.isCustom, undefined, "Custom property");
+
+const custom_segmenter = new CustomSegmenter(locale);
+assert.sameValue(custom_segmenter.isCustom, true, "Custom property");
+
+assert.compareArray(segments(custom_segmenter.segment(value)),
+ segments(real_segmenter.segment(value)),
+ "Direct call");
+
+assert.compareArray(segments(Intl.Segmenter.prototype.segment.call(custom_segmenter, value)),
+ segments(Intl.Segmenter.prototype.segment.call(real_segmenter, value)),
+ "Indirect call");
+
+assert.sameValue(Object.getPrototypeOf(custom_segmenter), CustomSegmenter.prototype, "Prototype");
+assert.sameValue(Object.getPrototypeOf(CustomSegmenter), Intl.Segmenter,
+ "Object.getPrototypeOf(CustomSegmenter) returns Intl.Segmenter");
+assert.sameValue(Object.getPrototypeOf(CustomSegmenter.prototype), Intl.Segmenter.prototype,
+ "Object.getPrototypeOf(CustomSegmenter.prototype) returns Intl.Segmenter.prototype");
+assert.sameValue(custom_segmenter instanceof Intl.Segmenter, true,
+ "The result of `custom_segmenter instanceof Intl.Segmenter` is true");
+
+assert.throws(TypeError, function() {
+ CustomSegmenter();
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/length.js b/js/src/tests/test262/intl402/Segmenter/constructor/length.js
new file mode 100644
index 0000000000..97bcaf7a38
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/length.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: >
+ Checks the "length" property of the Segmenter constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Segmenter constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/name.js b/js/src/tests/test262/intl402/Segmenter/constructor/name.js
new file mode 100644
index 0000000000..f055578fe8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Checks the "name" property of the Segmenter constructor.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter, "name", {
+ value: "Segmenter",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/constructor/prop-desc.js
new file mode 100644
index 0000000000..bab8f0d3a4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/prop-desc.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Verifies the "Segmenter" property of Intl.
+info: |
+ Requirements for Standard Built-in ECMAScript Objects
+
+ Unless specified otherwise in this document, the objects, functions, and constructors
+ described in this standard are subject to the generic requirements and restrictions
+ specified for standard built-in ECMAScript objects in the ECMAScript 2018 Language
+ Specification, 9th edition, clause 17, or successor.
+
+ ECMAScript Standard Built-in Objects:
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the
+ attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }
+ unless otherwise specified.
+
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter, "function");
+
+verifyProperty(Intl, "Segmenter", {
+ value: Intl.Segmenter,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/prototype.js b/js/src/tests/test262/intl402/Segmenter/constructor/prototype.js
new file mode 100644
index 0000000000..7725c52654
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/prototype.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: The prototype of the Intl.Segmenter constructor is %FunctionPrototype%.
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Unless otherwise specified every built-in function object has the %FunctionPrototype% object as the initial value of its [[Prototype]] internal slot.
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(
+ Object.getPrototypeOf(Intl.Segmenter),
+ Function.prototype,
+ "Object.getPrototypeOf(Intl.Segmenter) equals the value of Function.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/shell.js b/js/src/tests/test262/intl402/Segmenter/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/basic.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/basic.js
new file mode 100644
index 0000000000..d2f847e0e1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/basic.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Tests that Intl.Segmenter has a supportedLocalesOf property, and it works as expected.
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "supportedLocalesOf should be supported.");
+
+const defaultLocale = new Intl.Segmenter().resolvedOptions().locale;
+const notSupported = "zxx"; // "no linguistic content"
+const requestedLocales = [defaultLocale, notSupported];
+
+const supportedLocales = Intl.Segmenter.supportedLocalesOf(requestedLocales);
+assert.sameValue(supportedLocales.length, 1, "The length of supported locales list is not 1.");
+assert.sameValue(supportedLocales[0], defaultLocale, "The default locale is not returned in the supported list.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/branding.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/branding.js
new file mode 100644
index 0000000000..dee2601595
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/branding.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: >
+ Verifies there's no branding check for Intl.Segmenter.supportedLocalesOf().
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+features: [Intl.Segmenter]
+---*/
+
+const supportedLocalesOf = Intl.Segmenter.supportedLocalesOf;
+
+assert.sameValue(typeof supportedLocalesOf, "function");
+
+const thisValues = [
+ undefined,
+ null,
+ true,
+ "",
+ Symbol(),
+ 1,
+ {},
+ Intl.Segmenter,
+ Intl.Segmenter.prototype,
+];
+
+for (const thisValue of thisValues) {
+ const result = supportedLocalesOf.call(thisValue);
+ assert.sameValue(Array.isArray(result), true);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/browser.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/length.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/length.js
new file mode 100644
index 0000000000..e9bcaa18bc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/length.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: >
+ Checks the "length" property of Intl.Segmenter.supportedLocalesOf().
+info: |
+ The value of the length property of the supportedLocalesOf method is 1.
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, has a length property whose value is an integer.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.supportedLocalesOf, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-empty.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-empty.js
new file mode 100644
index 0000000000..642ab7f744
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-empty.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of an empty locales argument to the supportedLocalesOf function.
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+
+ 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+assert.compareArray(Intl.Segmenter.supportedLocalesOf(), []);
+assert.compareArray(Intl.Segmenter.supportedLocalesOf([]), []);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-invalid.js
new file mode 100644
index 0000000000..a8cc8ffbde
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-invalid.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks error cases for the locales argument to the supportedLocalesOf function.
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+
+ 2. Let requestedLocales be CanonicalizeLocaleList(locales).
+includes: [testIntl.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+for (const [locales, expectedError] of getInvalidLocaleArguments()) {
+ assert.throws(expectedError, () => Intl.Segmenter.supportedLocalesOf(locales));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-specific.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-specific.js
new file mode 100644
index 0000000000..17970524f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/locales-specific.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of specific locales arguments to the supportedLocalesOf function.
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+
+ 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
+includes: [compareArray.js]
+locale: [sr, sr-Thai-RS, de, zh-CN]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+assert.compareArray(Intl.Segmenter.supportedLocalesOf("sr"), ["sr"]);
+
+const multiLocale = ["sr-Thai-RS", "de", "zh-CN"];
+assert.compareArray(Intl.Segmenter.supportedLocalesOf(multiLocale, {localeMatcher: "lookup"}), multiLocale);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/name.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/name.js
new file mode 100644
index 0000000000..c4fdcfff14
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/name.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: >
+ Checks the "name" property of Intl.Segmenter.supportedLocalesOf().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.supportedLocalesOf, "name", {
+ value: "supportedLocalesOf",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-localeMatcher-invalid.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
new file mode 100644
index 0000000000..02d1699849
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-localeMatcher-invalid.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of invalid values for the localeMatcher option to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+const invalidOptions = [
+ null,
+ 1,
+ "",
+ "Lookup",
+ "LOOKUP",
+ "lookup\0",
+ "Best fit",
+ "BEST FIT",
+ "best\u00a0fit",
+];
+
+for (const invalidOption of invalidOptions) {
+ assert.throws(RangeError, function() {
+ Intl.Segmenter.supportedLocalesOf([], {"localeMatcher": invalidOption});
+ }, `${invalidOption} is an invalid localeMatcher option value`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-null.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-null.js
new file mode 100644
index 0000000000..f97ad6627f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-null.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of a null options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+assert.throws(TypeError, function() {
+ Intl.Segmenter.supportedLocalesOf([], null);
+}, "Should throw when passing null as the options argument");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-toobject.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-toobject.js
new file mode 100644
index 0000000000..db621a80c6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-toobject.js
@@ -0,0 +1,44 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of non-object options arguments to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ a. Let options be ? ToObject(options).
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+let called;
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() {
+ ++called;
+ return "best fit";
+ }
+ }
+});
+
+const optionsArguments = [
+ true,
+ "test",
+ 7,
+ Symbol(),
+];
+
+for (const options of optionsArguments) {
+ called = 0;
+ const result = Intl.Segmenter.supportedLocalesOf([], options);
+ assert.sameValue(Array.isArray(result), true, `Expected array from ${String(options)}`);
+ assert.sameValue(called, 1, `Expected one call from ${String(options)}`);
+}
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-undefined.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-undefined.js
new file mode 100644
index 0000000000..b81006fe81
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/options-undefined.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Checks handling of an undefined options argument to the supportedLocalesOf function.
+info: |
+ SupportedLocales ( availableLocales, requestedLocales, options )
+
+ 1. If options is not undefined, then
+ b. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(typeof Intl.Segmenter.supportedLocalesOf, "function",
+ "Should support Intl.Segmenter.supportedLocalesOf.");
+
+Object.defineProperties(Object.prototype, {
+ "localeMatcher": {
+ get() { throw new Error("Should not call localeMatcher getter"); }
+ }
+});
+
+assert.sameValue(Array.isArray(Intl.Segmenter.supportedLocalesOf()), true, "No arguments");
+assert.sameValue(Array.isArray(Intl.Segmenter.supportedLocalesOf([])), true, "One argument");
+assert.sameValue(Array.isArray(Intl.Segmenter.supportedLocalesOf([], undefined)), true, "Two arguments");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/prop-desc.js
new file mode 100644
index 0000000000..ac246193b2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/prop-desc.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: >
+ Checks the "supportedLocalesOf" property of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(
+ typeof Intl.Segmenter.supportedLocalesOf,
+ "function",
+ "typeof Intl.Segmenter.supportedLocalesOf is function"
+);
+
+verifyProperty(Intl.Segmenter, "supportedLocalesOf", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/result-type.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/result-type.js
new file mode 100644
index 0000000000..f476c36251
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/result-type.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.supportedLocalesOf
+description: Verifies the type of the return value of Intl.Segmenter.supportedLocalesOf().
+info: |
+ Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+const result = Intl.Segmenter.supportedLocalesOf("en");
+assert.sameValue(Array.isArray(result), true,
+ "Array.isArray() should return true");
+assert.sameValue(Object.getPrototypeOf(result), Array.prototype,
+ "The prototype should be Array.prototype");
+assert.sameValue(Object.isExtensible(result), true,
+ "Object.isExtensible() should return true");
+
+assert.notSameValue(result.length, 0);
+for (let i = 0; i < result.length; ++i) {
+ verifyProperty(result, String(i), {
+ "writable": true,
+ "enumerable": true,
+ "configurable": true,
+ });
+}
+
+verifyProperty(result, "length", {
+ "enumerable": false,
+ "configurable": false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/shell.js b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/constructor/supportedLocalesOf/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/ctor-custom-get-prototype-poison-throws.js b/js/src/tests/test262/intl402/Segmenter/ctor-custom-get-prototype-poison-throws.js
new file mode 100644
index 0000000000..3ad69bb425
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/ctor-custom-get-prototype-poison-throws.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Return abrupt from Get Prototype from a custom NewTarget
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ ...
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+ 3. Let proto be ? Get(constructor, "prototype").
+features: [Intl.Segmenter, Reflect, Proxy]
+---*/
+
+const custom = new Proxy(new Function(), {
+ get(target, key) {
+ if (key === 'prototype') {
+ throw new Test262Error();
+ }
+
+ return target[key];
+ }
+});
+
+assert.throws(Test262Error, () => {
+ Reflect.construct(Intl.Segmenter, [], custom);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/ctor-custom-prototype.js b/js/src/tests/test262/intl402/Segmenter/ctor-custom-prototype.js
new file mode 100644
index 0000000000..394a457fab
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/ctor-custom-prototype.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Custom Prototype of the returned object based on the NewTarget
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ ...
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+ 3. Let proto be ? Get(constructor, "prototype").
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [Intl.Segmenter, Reflect]
+---*/
+
+var custom = new Function();
+custom.prototype = {};
+
+const obj = Reflect.construct(Intl.Segmenter, [], custom);
+
+assert.sameValue(Object.getPrototypeOf(obj), custom.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/ctor-default-prototype.js b/js/src/tests/test262/intl402/Segmenter/ctor-default-prototype.js
new file mode 100644
index 0000000000..053b890eac
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/ctor-default-prototype.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: >
+ Prototype of the returned object is Segmenter.prototype
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+features: [Intl.Segmenter]
+---*/
+
+var obj = new Intl.Segmenter();
+
+assert.sameValue(Object.getPrototypeOf(obj), Intl.Segmenter.prototype);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/instance/browser.js b/js/src/tests/test262/intl402/Segmenter/instance/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/instance/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/instance/extensibility.js b/js/src/tests/test262/intl402/Segmenter/instance/extensibility.js
new file mode 100644
index 0000000000..2254e7f996
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/instance/extensibility.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Intl.Segmenter instance object extensibility
+info: |
+ 17 ECMAScript Standard Built-in Objects:
+
+ Unless specified otherwise, the [[Extensible]] internal slot
+ of a built-in object initially has the value true.
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(
+ Object.isExtensible(new Intl.Segmenter()),
+ true,
+ "Object.isExtensible(new Intl.Segmenter()) returns true"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/instance/prototype.js b/js/src/tests/test262/intl402/Segmenter/instance/prototype.js
new file mode 100644
index 0000000000..4a5af5c73e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/instance/prototype.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter
+description: Intl.Segmenter instance object is created from %SegmenterPrototype%.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+
+ 2. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%SegmenterPrototype%", « [[InitializedSegmenter]] »).
+features: [Intl.Segmenter]
+---*/
+
+const value = new Intl.Segmenter();
+assert.sameValue(
+ Object.getPrototypeOf(value),
+ Intl.Segmenter.prototype,
+ "Object.getPrototypeOf(value) equals the value of Intl.Segmenter.prototype"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/instance/shell.js b/js/src/tests/test262/intl402/Segmenter/instance/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/instance/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/proto-from-ctor-realm.js b/js/src/tests/test262/intl402/Segmenter/proto-from-ctor-realm.js
new file mode 100644
index 0000000000..4466d2e9c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/proto-from-ctor-realm.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright (C) 2019 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter
+description: Default [[Prototype]] value derived from realm of the NewTarget.
+info: |
+ Intl.Segmenter ([ locales [ , options ]])
+ 1. If NewTarget is undefined, throw a TypeError exception.
+ 3. Let segmenter be ? OrdinaryCreateFromConstructor(NewTarget, "%Segmenter.prototype%", internalSlotsList).
+ ...
+ 15. Return segmenter.
+ OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )
+ ...
+ 2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
+ 3. Return ObjectCreate(proto, internalSlotsList).
+ GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
+ ...
+ 3. Let proto be ? Get(constructor, 'prototype').
+ 4. If Type(proto) is not Object, then
+ a. Let realm be ? GetFunctionRealm(constructor).
+ b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
+ 5. Return proto.
+features: [cross-realm, Reflect, Symbol, Intl.Segmenter]
+---*/
+
+const other = $262.createRealm().global;
+const newTarget = new other.Function();
+let sgm;
+
+newTarget.prototype = undefined;
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is undefined');
+
+newTarget.prototype = null;
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is null');
+
+newTarget.prototype = false;
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Boolean');
+
+newTarget.prototype = 'str';
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is a String');
+
+newTarget.prototype = Symbol();
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Symbol');
+
+newTarget.prototype = 1;
+sgm = Reflect.construct(Intl.Segmenter, [], newTarget);
+assert.sameValue(Object.getPrototypeOf(sgm), other.Intl.Segmenter.prototype, 'newTarget.prototype is a Number');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/Symbol.toStringTag.js b/js/src/tests/test262/intl402/Segmenter/prototype/Symbol.toStringTag.js
new file mode 100644
index 0000000000..2960428e22
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/Symbol.toStringTag.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.segmenter.prototype-@@tostringtag
+description: >
+ Property descriptor of Segmenter.prototype[@@toStringTag]
+info: |
+ The initial value of the @@toStringTag property is the string value "Intl.Segmenter".
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype, Symbol.toStringTag, {
+ value: "Intl.Segmenter",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/constructor/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/constructor/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/prop-desc.js
new file mode 100644
index 0000000000..566e36a669
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/prop-desc.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.constructor
+description: Checks the "constructor" property of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.constructor
+
+ The initial value of Intl.Segmenter.prototype.constructor is %Segmenter%.
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype, "constructor", {
+ value: Intl.Segmenter,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/constructor/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/constructor/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/prototype/prop-desc.js
new file mode 100644
index 0000000000..34839605ec
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype
+description: Checks the "prototype" property of the Segmenter constructor.
+info: |
+ Intl.Segmenter.prototype
+
+ The value of Intl.Segmenter.prototype is %SegmenterPrototype%.
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter, "prototype", {
+ writable: false,
+ enumerable: false,
+ configurable: false,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/branding.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/branding.js
new file mode 100644
index 0000000000..756a9132d2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/branding.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Verifies the branding check for the "resolvedOptions" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.resolvedOptions ()
+
+ 2. If Type(pr) is not Object or pr does not have an [[InitializedSegmenter]] internal slot, throw a TypeError exception.
+features: [Intl.Segmenter]
+---*/
+
+const resolvedOptions = Intl.Segmenter.prototype.resolvedOptions;
+
+assert.sameValue(typeof resolvedOptions, "function");
+
+assert.throws(TypeError, () => resolvedOptions.call(undefined), "undefined");
+assert.throws(TypeError, () => resolvedOptions.call(null), "null");
+assert.throws(TypeError, () => resolvedOptions.call(true), "true");
+assert.throws(TypeError, () => resolvedOptions.call(""), "empty string");
+assert.throws(TypeError, () => resolvedOptions.call(Symbol()), "symbol");
+assert.throws(TypeError, () => resolvedOptions.call(1), "1");
+assert.throws(TypeError, () => resolvedOptions.call({}), "plain object");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.Segmenter), "Intl.Segmenter");
+assert.throws(TypeError, () => resolvedOptions.call(Intl.Segmenter.prototype), "Intl.Segmenter.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/caching.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/caching.js
new file mode 100644
index 0000000000..96c76ec704
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/caching.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Verifies that the return value of Intl.Segmenter.prototype.resolvedOptions() is not cached.
+info: |
+ Intl.Segmenter.prototype.resolvedOptions ()
+
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+features: [Intl.Segmenter]
+---*/
+
+const s = new Intl.Segmenter("en-us");
+const options1 = s.resolvedOptions();
+const options2 = s.resolvedOptions();
+assert.notSameValue(options1, options2, "Should create a new object each time.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/length.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/length.js
new file mode 100644
index 0000000000..e43dea0c6d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/length.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Checks the "length" property of Intl.Segmenter.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Segmenter constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype.resolvedOptions, "length", {
+ value: 0,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/name.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/name.js
new file mode 100644
index 0000000000..041afb9d92
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Checks the "name" property of Intl.Segmenter.prototype.resolvedOptions().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype.resolvedOptions, "name", {
+ value: "resolvedOptions",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/order.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/order.js
new file mode 100644
index 0000000000..aa254958c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/order.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Verifies the property order for the object returned by resolvedOptions().
+features: [Intl.Segmenter]
+---*/
+
+const options = new Intl.Segmenter([], {
+ "granularity": "word",
+}).resolvedOptions();
+
+const expected = [
+ "locale",
+ "granularity",
+];
+
+const actual = Object.getOwnPropertyNames(options);
+
+// Ensure all expected items are in actual and also allow other properties
+// implemented in new proposals.
+assert(actual.indexOf("locale") > -1, "\"locale\" is present");
+for (var i = 1; i < expected.length; i++) {
+ // Ensure the order as expected but allow additional new property in between
+ assert(actual.indexOf(expected[i-1]) < actual.indexOf(expected[i]), `"${expected[i-1]}" precedes "${expected[i]}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/prop-desc.js
new file mode 100644
index 0000000000..d210777575
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/prop-desc.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Checks the "resolvedOptions" property of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.resolvedOptions ()
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(
+ typeof Intl.Segmenter.prototype.resolvedOptions,
+ "function",
+ "typeof Intl.Segmenter.prototype.resolvedOptions is function"
+);
+
+verifyProperty(Intl.Segmenter.prototype, "resolvedOptions", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/type-without-lbs.js b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/type-without-lbs.js
new file mode 100644
index 0000000000..b12e734dba
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/resolvedOptions/type-without-lbs.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.resolvedOptions
+description: Checks the properties of the result of Intl.Segmenter.prototype.resolvedOptions().
+info: |
+ Intl.Segmenter.prototype.resolvedOptions ()
+
+ 3. Let options be ! ObjectCreate(%ObjectPrototype%).
+ 4. For each row of Table 1, except the header row, do
+ c. If v is not undefined, then
+ i. Perform ! CreateDataPropertyOrThrow(options, p, v).
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+const rtf = new Intl.Segmenter("en-us", { "lineBreakStyle": "loose", "granularity": "word" });
+const options = rtf.resolvedOptions();
+assert.sameValue(Object.getPrototypeOf(options), Object.prototype, "Prototype");
+
+verifyProperty(options, "locale", {
+ value: "en-US",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "granularity", {
+ value: "word",
+ writable: true,
+ enumerable: true,
+ configurable: true,
+});
+
+verifyProperty(options, "lineBreakStyle", undefined);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/branding.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/branding.js
new file mode 100644
index 0000000000..7bb5ab5ae5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/branding.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Verifies the branding check for the "segment" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+
+ 2. If Type(segment) is not Object or segment does not have an [[InitializedSegmenter]] internal slot, throw a TypeError exception.
+features: [Intl.Segmenter]
+---*/
+
+const segment = Intl.Segmenter.prototype.segment;
+
+assert.sameValue(typeof segment, "function");
+
+assert.throws(TypeError, () => segment.call(undefined), "undefined");
+assert.throws(TypeError, () => segment.call(null), "null");
+assert.throws(TypeError, () => segment.call(true), "true");
+assert.throws(TypeError, () => segment.call(""), "empty string");
+assert.throws(TypeError, () => segment.call(Symbol()), "symbol");
+assert.throws(TypeError, () => segment.call(1), "1");
+assert.throws(TypeError, () => segment.call({}), "plain object");
+assert.throws(TypeError, () => segment.call(Intl.Segmenter), "Intl.Segmenter");
+assert.throws(TypeError, () => segment.call(Intl.Segmenter.prototype), "Intl.Segmenter.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/branding.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/branding.js
new file mode 100644
index 0000000000..d3b323ead2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/branding.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the branding check for the "segment" function of the %Segments.prototype%.containing.
+info: |
+ %Segments.prototype%.containing ( index )
+ 1. Let segments be the this value.
+ 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]).
+
+features: [Intl.Segmenter]
+---*/
+const segment = (new Intl.Segmenter()).segment("123");
+const containing = segment.containing;
+assert.sameValue(typeof containing, "function");
+assert.throws(TypeError, () => containing.call(undefined), "undefined");
+assert.throws(TypeError, () => containing.call(null), "null");
+assert.throws(TypeError, () => containing.call(true), "true");
+assert.throws(TypeError, () => containing.call(""), "empty string");
+assert.throws(TypeError, () => containing.call(Symbol()), "symbol");
+assert.throws(TypeError, () => containing.call(1), "1");
+assert.throws(TypeError, () => containing.call({}), "plain object");
+assert.throws(TypeError, () => containing.call(Intl.Segmenter), "Intl.Segmenter");
+assert.throws(TypeError, () => containing.call(Intl.Segmenter.prototype), "Intl.Segmenter.prototype");
+assert.sameValue(undefined, containing.call(segment, -1));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/breakable-input.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/breakable-input.js
new file mode 100644
index 0000000000..d1f50c5b9c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/breakable-input.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the input is breakable.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+ 9. Let endIndex be ! FindBoundary(segmenter, string, n, after).
+
+features: [Intl.Segmenter]
+---*/
+
+// The inputs are breakable for "grapheme" and "word" but not for "sentence"
+const granularities = [undefined, "grapheme", "word"];
+// The following all contains more than one segments in either "grapheme" or "word"
+// granularity.
+const inputs = [
+ "123 ",
+ "a ",
+ " a",
+ " \ud800\udc00", // SPACE + surrogate
+ "\ud800\udc00 ", // surrogate + SPACE
+ "\udc00\ud800", // incorrect surrogate- tail + leading
+ "\ud800 ", // only leading surrogate + SPACE
+ "\udc00 ", // only trailing surrogate + SPACE
+ " \ud800", // SPACE + only leading surrogate
+ " \udc00", // SPACE + only trailing surrogate
+ " 台", // SPACE + a Han character
+ "台 ", // a Han character + SPACE
+ "\u0301 ", // a modifier + SPACE
+];
+
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ inputs.forEach(function(input) {
+ const segment = segmenter.segment(input);
+ let msg = `granularity: ${granularity} input: ${input}`;
+ const first = segment.containing(0);
+ assert.sameValue(0, first.index, `${msg} containing(0) index`);
+ assert.sameValue(input, first.input, `${msg} containing(0) input`);
+ assert.sameValue(false, first.segment == input,
+ `${msg} containing(0) segment`);
+ const last = segment.containing(input.length - 1);
+ msg += ` containing(${input.length - 1}) `
+ assert.sameValue(true, last.index > 0, `${msg} index > 0`);
+ assert.sameValue(true, last.index < input.length, `${msg} index`);
+ assert.sameValue(input, last.input, `${msg} input`);
+ assert.sameValue(false, last.segment == input, `${msg} segment`);
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/index-throws.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/index-throws.js
new file mode 100644
index 0000000000..2948b6a242
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/index-throws.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the value of index which throws.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 6. Let n be ? ToInteger(index).
+ 7. If n < 0 or n ≥ len, return undefined.
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+
+ ToInteger ( argument )
+ 1. Let number be ? ToNumber(argument).
+
+ ToNumber ( argument )
+ Symbol | Throw a TypeError exception.
+ BigInt | Throw a TypeError exception.
+
+features: [Intl.Segmenter]
+---*/
+
+const input = "a b c";
+const granularities = [undefined, "grapheme", "word", "sentence"];
+const index_throws = [
+ // Symbol
+ Symbol(),
+ // BigInt
+ 0n,
+ -1n,
+ 1n,
+ BigInt(0),
+ BigInt(1),
+ BigInt(-1),
+ BigInt(input.length),
+];
+
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ const segment = segmenter.segment(input);
+ index_throws.forEach(function(index) {
+ assert.throws(TypeError, () => {segment.containing(index);})
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/iswordlike.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/iswordlike.js
new file mode 100644
index 0000000000..599e95aa27
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/iswordlike.js
@@ -0,0 +1,59 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the isWordLike in the result when granularity is not "word".
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 10. Return ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex).
+
+ CreateSegmentDataObject ( segmenter, string, startIndex, endIndex )
+ 11. If granularity is "word", then
+ a. Let isWordLike be a Boolean value indicating whether the segment in string is "word-like" according to locale segmenter.[[Locale]].
+ b. Perform ! CreateDataPropertyOrThrow(result, "isWordLike", isWordLike).
+
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+const other_granularities = [undefined, "grapheme", "sentence"];
+// Some text
+const inputs = [
+ "Hello world!", // English
+ "Jedovatou mambu objevila žena v zahrádkářské kolonii.", // Czech
+ "Việt Nam: Nhất thể hóa sẽ khác Trung Quốc?", // Vietnamese
+ "Σοβαρές ενστάσεις Κομισιόν για τον προϋπολογισμό της Ιταλίας", // Greek
+ "Решение Индии о покупке российских С-400 расценили как вызов США", // Russian
+ "הרופא שהציל נשים והנערה ששועבדה ע", // Hebrew,
+ "ترامب للملك سلمان: أنا جاد للغاية.. عليك دفع المزيد", // Arabic
+ "भारत की एस 400 मिसाइल के मुकाबले पाक की थाड, जानें कौन कितना ताकतवर", // Hindi
+ "ரெட் அலர்ட் எச்சரிக்கை; புதுச்சேரியில் நாளை அரசு விடுமுறை!", // Tamil
+ "'ఉత్తర్వులు అందే వరకు ఓటర్ల తుది జాబితాను వెబ్‌సైట్లో పెట్టవద్దు'", // Telugu
+ "台北》抹黑柯P失敗?朱學恒酸:姚文智氣pupu嗆大老闆", // Chinese
+ "วัดไทรตีระฆังเบาลงช่วงเข้าพรรษา เจ้าอาวาสเผยคนร้องเรียนรับผลกรรมแล้ว", // Thai
+ "九州北部の一部が暴風域に入りました(日直予報士 2018年10月06日) - 日本気象協会 tenki.jp", // Japanese
+ "법원 “다스 지분 처분권·수익권 모두 MB가 보유”", // Korean
+];
+
+other_granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ inputs.forEach(function(input) {
+ const segment = segmenter.segment(input);
+ for (let index = 0; index < input.length; index++) {
+ const result = segment.containing(index);
+ const msg =
+ `granularity: ${granularity} input: ${input} containing(${index})`;
+ assert.sameValue(true, result.index >= 0, `${msg} index >= 0`);
+ assert.sameValue(true, result.index < input.length, `${msg} index`);
+ assert.sameValue("string", typeof result.input, `${msg} input`);
+ assert.sameValue(undefined, result.isWordLike,
+ `${msg} isWordLike should be undefined`);
+ assert.compareArray(Object.getOwnPropertyNames(result), ["segment", "index", "input"]);
+ }
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/length.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/length.js
new file mode 100644
index 0000000000..e42cf8f308
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/length.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Checks the "length" property of %Segments.prototype%.containing()
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Segmenter constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+const segment = (new Intl.Segmenter()).segment("");
+verifyProperty(segment.containing, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/name.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/name.js
new file mode 100644
index 0000000000..e5d4bb9403
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/name.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Checks the "name" property of %Segments.prototype%.containing ( index )
+info: |
+ %Segments.prototype%.containing ( index )
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2020 Language Specification, 11th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+const segment = (new Intl.Segmenter()).segment("");
+verifyProperty(segment.containing, "name", {
+ value: "containing",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/one-index.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/one-index.js
new file mode 100644
index 0000000000..b23683ef99
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/one-index.js
@@ -0,0 +1,70 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the value of index turn into 1.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 6. Let n be ? ToInteger(index).
+ 7. If n < 0 or n ≥ len, return undefined.
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+
+ ToInteger ( argument )
+ 1. Let number be ? ToNumber(argument).
+ 2. If number is NaN, +0, or -0, return +0.
+ 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
+ 5. If integer is -0, return +0.
+ 6. Return integer.
+
+ ToNumber ( argument )
+ Undefined | Return NaN.
+ Null | Return +0.
+ Boolean | If argument is true, return 1. If argument is false, return +0.
+
+features: [Intl.Segmenter]
+---*/
+
+const input = "a c";
+const granularities = [undefined, "grapheme", "word"];
+const index_to_one = [
+ 1,
+ 1.49,
+ 14.9E-1,
+ 14.9e-1,
+ "1.49",
+ "14.9E-1",
+ "14.9e-1",
+ true,
+ { toString(){ return "1"; } },
+ { valueOf(){ return 1; } },
+ { [Symbol.toPrimitive](){ return 1; } },
+];
+
+// Except granularity: "sentence", check the result.segment is " ".
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ const segment = segmenter.segment(input);
+ index_to_one.forEach(function(index) {
+ const result = segment.containing(index);
+ const msg = "granularity: " + granularity + " index: " + index;
+ assert.sameValue(1, result.index, msg + " index");
+ assert.sameValue(" ", result.segment, msg + " segment");
+ assert.sameValue(input, result.input, msg + " input");
+ });
+ });
+
+// For granularity: "sentence", result.segment is input
+const segmenter = new Intl.Segmenter(undefined, {granularity: "sentence"});
+const segment = segmenter.segment(input);
+index_to_one.forEach(function(index) {
+ const result = segment.containing(index);
+ const msg = "granularity: sentence index: " + index;
+ assert.sameValue(0, result.index, msg + " index");
+ assert.sameValue(input, result.segment, msg + " segment");
+ assert.sameValue(input, result.input, msg + " input");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/out-of-bound-index.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/out-of-bound-index.js
new file mode 100644
index 0000000000..196de36d7f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/out-of-bound-index.js
@@ -0,0 +1,56 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the value of index turn into out of bound.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 6. Let n be ? ToInteger(index).
+ 7. If n < 0 or n ≥ len, return undefined.
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+
+ ToInteger ( argument )
+ 1. Let number be ? ToNumber(argument).
+ 2. If number is NaN, +0, or -0, return +0.
+ 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
+ 5. If integer is -0, return +0.
+ 6. Return integer.
+
+ ToNumber ( argument )
+ String | See grammar and conversion algorithm below.
+
+features: [Intl.Segmenter]
+---*/
+
+const input = "a b c";
+const granularities = [undefined, "grapheme", "word", "sentence"];
+const index_to_out_of_bound = [
+ input.length,
+ input.length + 0.1,
+ -1,
+ -2,
+ "-1",
+ "-2",
+ "-1.1",
+ Infinity,
+ -Infinity,
+ "Infinity",
+ "-Infinity",
+ { toString(){ return "-1"; } },
+ { valueOf(){ return input.length; } },
+ { [Symbol.toPrimitive](){ return -1; } },
+];
+
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ const segment = segmenter.segment(input);
+ index_to_out_of_bound.forEach(function(index) {
+ const result = segment.containing(index);
+ assert.sameValue(undefined, result);
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/prop-desc.js
new file mode 100644
index 0000000000..dd39c1862b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Checks the "containing" property of the %Segments.prototype% object.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+features: [Intl.Segmenter]
+---*/
+
+const segment = (new Intl.Segmenter()).segment("");
+assert.sameValue(
+ typeof segment.containing,
+ "function",
+ "typeof %Segments.prototype%.containing is function"
+);
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/unbreakable-input.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/unbreakable-input.js
new file mode 100644
index 0000000000..bcc43b81f7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/unbreakable-input.js
@@ -0,0 +1,60 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the input is unbreakable.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+ 9. Let endIndex be ! FindBoundary(segmenter, string, n, after).
+
+features: [Intl.Segmenter]
+---*/
+
+const granularities = [undefined, "grapheme", "word", "sentence"];
+// The following all contains only one segment in any granularity.
+const inputs = [
+ "a",
+ " ",
+ "\ud800\udc00", // surrogate
+ "\ud800", // only leading surrogate
+ "\udc00", // only trailing surrogate
+ "台", // a Han character
+ "\u0301", // a modifier
+ "a\u0301", // ASCII + a modifier
+ "ซิ่", // a Thai cluster
+ "𐂰", // a Surrogate pair
+ "\uD83D\uDC4B\uD83C\uDFFB", // Emoji short sequence: waving_hand_light_skin_tone
+ "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB0", // Emoji long sequence: man_light_skin_tone_red_hair
+ "\u1102", // Jamo L
+ "\u1162", // Jamo V
+ "\u11A9", // Jamo T
+ "\u1102\u1162", // Jamo LV
+ "\u1102\u1162\u11A9", // Jamo LVT
+ "\u1102\u1102", // Jamo L L
+ "\u1102\u1102\u1162", // Jamo L L V
+ "\u1102\u1102\u1162\u11A9", // Jamo L L V T
+ "\u1162\u1162", // Jamo V V
+ "\u1162\u11A9", // Jamo V T
+ "\u1102\u1162\u1162", // Jamo V V
+ "\u11A9\u11A9", // Jamo T T
+ "\u1102\u1162\u11A9\u11A9", // Jamo LVT T
+];
+
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ inputs.forEach(function(input) {
+ const segment = segmenter.segment(input);
+ for (let index = 0; index < input.length; index++) {
+ const result = segment.containing(index);
+ assert.sameValue(0, result.index);
+ assert.sameValue(input, result.input);
+ assert.sameValue(input, result.segment);
+ }
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/word-iswordlike.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/word-iswordlike.js
new file mode 100644
index 0000000000..3124a65619
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/word-iswordlike.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the isWordLike in the result when granularity is "word".
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 10. Return ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex).
+
+ CreateSegmentDataObject ( segmenter, string, startIndex, endIndex )
+ 11. If granularity is "word", then
+ a. Let isWordLike be a Boolean value indicating whether the segment in string is "word-like" according to locale segmenter.[[Locale]].
+ b. Perform ! CreateDataPropertyOrThrow(result, "isWordLike", isWordLike).
+
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+// Some text
+const inputs = [
+ "Hello world!", // English
+ "Jedovatou mambu objevila žena v zahrádkářské kolonii.", // Czech
+ "Việt Nam: Nhất thể hóa sẽ khác Trung Quốc?", // Vietnamese
+ "Σοβαρές ενστάσεις Κομισιόν για τον προϋπολογισμό της Ιταλίας", // Greek
+ "Решение Индии о покупке российских С-400 расценили как вызов США", // Russian
+ "הרופא שהציל נשים והנערה ששועבדה ע", // Hebrew,
+ "ترامب للملك سلمان: أنا جاد للغاية.. عليك دفع المزيد", // Arabic
+ "भारत की एस 400 मिसाइल के मुकाबले पाक की थाड, जानें कौन कितना ताकतवर", // Hindi
+ "ரெட் அலர்ட் எச்சரிக்கை; புதுச்சேரியில் நாளை அரசு விடுமுறை!", // Tamil
+ "'ఉత్తర్వులు అందే వరకు ఓటర్ల తుది జాబితాను వెబ్‌సైట్లో పెట్టవద్దు'", // Telugu
+ "台北》抹黑柯P失敗?朱學恒酸:姚文智氣pupu嗆大老闆", // Chinese
+ "วัดไทรตีระฆังเบาลงช่วงเข้าพรรษา เจ้าอาวาสเผยคนร้องเรียนรับผลกรรมแล้ว", // Thai
+ "九州北部の一部が暴風域に入りました(日直予報士 2018年10月06日) - 日本気象協会 tenki.jp", // Japanese
+ "법원 “다스 지분 처분권·수익권 모두 MB가 보유”", // Korean
+];
+
+const granularity = "word";
+const segmenter = new Intl.Segmenter(undefined, {granularity});
+inputs.forEach(function(input) {
+ const segment = segmenter.segment(input);
+ for (let index = 0; index < input.length; index++) {
+ const result = segment.containing(index);
+ const msg = "granularity: " + granularity + " input: " + input +
+ " containing(" + index + ") ";
+ assert.sameValue(true, result.index >= 0, msg + "index >= 0");
+ assert.sameValue(true, result.index < input.length,
+ msg + "index < " + input.length);
+ assert.sameValue("string", typeof result.input, msg + "input");
+ assert.sameValue("boolean", typeof result.isWordLike,
+ msg + "isWordLike should be boolean");
+ assert.compareArray(Object.getOwnPropertyNames(result),
+ ["segment", "index", "input", "isWordLike"]);
+ }
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/zero-index.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/zero-index.js
new file mode 100644
index 0000000000..0e768bd224
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/containing/zero-index.js
@@ -0,0 +1,72 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-%segmentsprototype%.containing
+description: Verifies the cases which the value of index turn into 0.
+info: |
+ %Segments.prototype%.containing ( index )
+
+ 6. Let n be ? ToInteger(index).
+ 7. If n < 0 or n ≥ len, return undefined.
+ 8. Let startIndex be ! FindBoundary(segmenter, string, n, before).
+
+ ToInteger ( argument )
+ 1. Let number be ? ToNumber(argument).
+ 2. If number is NaN, +0, or -0, return +0.
+ 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
+ 5. If integer is -0, return +0.
+ 6. Return integer.
+
+ ToNumber ( argument )
+ Undefined | Return NaN.
+ Null | Return +0.
+ Boolean | If argument is true, return 1. If argument is false, return +0.
+
+features: [Intl.Segmenter]
+---*/
+
+const input = "a b c";
+const granularities = [undefined, "grapheme", "word", "sentence"];
+const index_to_zeros = [
+ 0,
+ -0,
+ NaN,
+ 0.49,
+ -0.49,
+ null,
+ undefined,
+ false,
+ "\ud800\udc00", // surrogate
+ "\ud800", // only leading surrogate
+ "\udc00", // only trailing surrogate
+ "a",
+ "g",
+ "\u00DD",
+ "0",
+ "+0",
+ "-0",
+ "0.49",
+ "+0.49",
+ "-0.49",
+ "4.9e-1",
+ "-4.9e-1",
+ "4.9E-1",
+ "-4.9E-1",
+ { toString(){ return "-0.1"; } },
+ { valueOf(){ return 0.1; } },
+ { [Symbol.toPrimitive](){ return -0.1; } },
+];
+
+granularities.forEach(
+ function(granularity) {
+ const segmenter = new Intl.Segmenter(undefined, {granularity});
+ const segment = segmenter.segment(input);
+ index_to_zeros.forEach(function(index) {
+ const result = segment.containing(index);
+ assert.sameValue(0, result.index);
+ assert.sameValue(input, result.input);
+ });
+ });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/length.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/length.js
new file mode 100644
index 0000000000..21db9d950e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/length.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Checks the "length" property of Intl.Segmenter.prototype.segment().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ The Segmenter constructor is a standard built-in property of the Intl object.
+ Every built-in function object, including constructors, has a length property whose value is an integer. Unless otherwise specified, this value is equal to the largest number of named arguments shown in the subclause headings for the function description. Optional parameters (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form «...name») are not included in the default argument count.
+ Unless otherwise specified, the length property of a built-in function object has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype.segment, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/name.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/name.js
new file mode 100644
index 0000000000..823f44f579
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/name.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Checks the "name" property of Intl.Segmenter.prototype.segment().
+info: |
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+ Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification.
+ Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype.segment, "name", {
+ value: "segment",
+ writable: false,
+ enumerable: false,
+ configurable: true
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/nested-next.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/nested-next.js
new file mode 100644
index 0000000000..6d21985021
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/nested-next.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-%segmentsprototype%-@@iterator
+description: Test to ensure the nested calling of the next method won't caused confusion to each other.
+info: |
+ %Segments.prototype% [ @@iterator ] ()
+ 5. Return ! CreateSegmentIterator(segmenter, string)
+
+ CreateSegmentIterator ( segmenter, string )
+ 1. Let internalSlotsList be « [[IteratingSegmenter]], [[IteratedString]], [[IteratedStringNextSegmentCodeUnitIndex]] ».
+ 2. Let iterator be ! ObjectCreate(%SegmentIterator.prototype%, internalSlotsList).
+ 3. Set iterator.[[IteratingSegmenter]] to segmenter.
+ 4. Set iterator.[[IteratedString]] to string.
+ 5. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to 0.
+ 6. Return iterator.
+
+ %SegmentIterator.prototype%.next ()
+ 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]].
+
+features: [Intl.Segmenter]
+---*/
+
+const segmenter = new Intl.Segmenter();
+const input = "ABCD";
+const segments = segmenter.segment(input);
+let result = "";
+for (let v1 of segments) {
+ for (let v2 of segments) {
+ result += v1.segment;
+ result += v2.segment;
+ }
+ result += ":";
+}
+assert.sameValue("AAABACAD:BABBBCBD:CACBCCCD:DADBDCDD:", result);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-inside-next.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-inside-next.js
new file mode 100644
index 0000000000..2936d7621e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-inside-next.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-%segmentsprototype%-@@iterator
+description: Test to ensure the next on two segments of the segmenter won't interfer each other.
+info: |
+ %Segments.prototype% [ @@iterator ] ()
+ 5. Return ! CreateSegmentIterator(segmenter, string)
+
+ CreateSegmentIterator ( segmenter, string )
+ 1. Let internalSlotsList be « [[IteratingSegmenter]], [[IteratedString]], [[IteratedStringNextSegmentCodeUnitIndex]] ».
+ 2. Let iterator be ! ObjectCreate(%SegmentIterator.prototype%, internalSlotsList).
+ 3. Set iterator.[[IteratingSegmenter]] to segmenter.
+ 4. Set iterator.[[IteratedString]] to string.
+ 5. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to 0.
+ 6. Return iterator.
+
+ %SegmentIterator.prototype%.next ()
+ 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]].
+
+features: [Intl.Segmenter]
+---*/
+
+const segmenter = new Intl.Segmenter();
+const input1 = "ABCD";
+const input2 = "123";
+const segments1 = segmenter.segment(input1);
+const segments2 = segmenter.segment(input2);
+let result = "";
+for (let v1 of segments1) {
+ for (let v2 of segments2) {
+ result += v1.segment;
+ result += v2.segment;
+ }
+ result += ":";
+}
+// Now loop segments2 .
+for (let v2 of segments2) {
+ for (let v1 of segments1) {
+ result += v2.segment;
+ result += v1.segment;
+ }
+ result += ":";
+}
+assert.sameValue(
+ "A1A2A3:B1B2B3:C1C2C3:D1D2D3:1A1B1C1D:2A2B2C2D:3A3B3C3D:", result);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-mix-with-containing.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-mix-with-containing.js
new file mode 100644
index 0000000000..dcc231c12b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/next-mix-with-containing.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2020 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-%segmentsprototype%-@@iterator
+description: Test to ensure the the calling of containing() won't impact the calling of the next().
+info: |
+ %Segments.prototype% [ @@iterator ] ()
+ 5. Return ! CreateSegmentIterator(segmenter, string)
+
+ CreateSegmentIterator ( segmenter, string )
+ 1. Let internalSlotsList be « [[IteratingSegmenter]], [[IteratedString]], [[IteratedStringNextSegmentCodeUnitIndex]] ».
+ 2. Let iterator be ! ObjectCreate(%SegmentIterator.prototype%, internalSlotsList).
+ 3. Set iterator.[[IteratingSegmenter]] to segmenter.
+ 4. Set iterator.[[IteratedString]] to string.
+ 5. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to 0.
+ 6. Return iterator.
+
+ %SegmentIterator.prototype%.next ()
+ 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]].
+
+ %Segments.prototype%.containing ( index )
+ 3. Let segmenter be segments.[[SegmentsSegmenter]].
+ 4. Let string be segments.[[SegmentsString]].
+
+
+features: [Intl.Segmenter]
+---*/
+
+const segmenter = new Intl.Segmenter();
+const input = "ABC";
+const segments = segmenter.segment(input);
+let next_result = "";
+for (let i = 0; i < input.length; i++) {
+ let containing_result = segments.containing(i);
+ let msg = "containing(" + i + ") before the loop. ";
+ assert.sameValue(input[i], containing_result.segment, msg + "segment");
+ assert.sameValue(i, containing_result.index, msg + "index");
+ assert.sameValue(input, containing_result.input, msg + "input");
+ for (let v of segments) {
+ next_result += v.segment;
+ next_result += ":";
+ // Ensure the value n passing into segments.containing(n) will not impact
+ // the result of next().
+ msg = "containing(" + i + ") inside the loop. ";
+ containing_result = segments.containing(i);
+ assert.sameValue(input[i], containing_result.segment, msg + "segment");
+ assert.sameValue(i, containing_result.index, msg + "index");
+ assert.sameValue(input, containing_result.input, msg + "input");
+ }
+}
+assert.sameValue("A:B:C:A:B:C:A:B:C:", next_result);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/prop-desc.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/prop-desc.js
new file mode 100644
index 0000000000..d760eab7e5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/prop-desc.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Checks the "segment" property of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+
+ Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
+
+ Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(
+ typeof Intl.Segmenter.prototype.segment,
+ "function",
+ "typeof Intl.Segmenter.prototype.segment is function"
+);
+
+verifyProperty(Intl.Segmenter.prototype, "segment", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-grapheme-iterable.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-grapheme-iterable.js
new file mode 100644
index 0000000000..5b3766ff3a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-grapheme-iterable.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Verifies the behavior for the "segment" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+const seg = new Intl.Segmenter([], {granularity: "grapheme"})
+for (const text of [
+ "Hello world!", // English
+ " Hello world! ", // English with space before/after
+ " Hello world? Foo bar!", // English
+ "Jedovatou mambu objevila žena v zahrádkářské kolonii.", // Czech
+ "Việt Nam: Nhất thể hóa sẽ khác Trung Quốc?", // Vietnamese
+ "Σοβαρές ενστάσεις Κομισιόν για τον προϋπολογισμό της Ιταλίας", // Greek
+ "Решение Индии о покупке российских С-400 расценили как вызов США", // Russian
+ "הרופא שהציל נשים והנערה ששועבדה ע", // Hebrew,
+ "ترامب للملك سلمان: أنا جاد للغاية.. عليك دفع المزيد", // Arabic
+ "भारत की एस 400 मिसाइल के मुकाबले पाक की थाड, जानें कौन कितना ताकतवर", // Hindi
+ "ரெட் அலர்ட் எச்சரிக்கை; புதுச்சேரியில் நாளை அரசு விடுமுறை!", // Tamil
+ "'ఉత్తర్వులు అందే వరకు ఓటర్ల తుది జాబితాను వెబ్‌సైట్లో పెట్టవద్దు'", // Telugu
+ "台北》抹黑柯P失敗?朱學恒酸:姚文智氣pupu嗆大老闆", // Chinese
+ "วัดไทรตีระฆังเบาลงช่วงเข้าพรรษา เจ้าอาวาสเผยคนร้องเรียนรับผลกรรมแล้ว", // Thai
+ "九州北部の一部が暴風域に入りました(日直予報士 2018年10月06日) - 日本気象協会 tenki.jp", // Japanese
+ "법원 “다스 지분 처분권·수익권 모두 MB가 보유”", // Korean
+ ]) {
+ let segments = [];
+ for (const v of seg.segment(text)) {
+ assert.sameValue("string", typeof v.segment);
+ assert.sameValue(true, v.segment.length > 0, "length > 0");
+
+ assert.sameValue("number", typeof v.index);
+ assert.sameValue(true, v.index >= 0, "index >= 0");
+ assert.sameValue(true, v.index < text.length, "index < input.length");
+
+ assert.sameValue("string", typeof v.input);
+ assert.sameValue(text, v.input);
+
+ assert.sameValue(undefined, v.isWordLike);
+ assert.sameValue(false, v.hasOwnProperty("isWordLike"));
+
+ assert.sameValue(text.slice(v.index, v.index + v.segment.length), v.segment);
+ assert.compareArray(Object.getOwnPropertyNames(v), ["segment", "index", "input"]);
+
+ segments.push(v.segment);
+ }
+ assert.sameValue(text, segments.join(''));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-sentence-iterable.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-sentence-iterable.js
new file mode 100644
index 0000000000..706e49b4c5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-sentence-iterable.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Verifies the behavior for the "segment" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+const seg = new Intl.Segmenter([], {granularity: "sentence"})
+for (const text of [
+ "Hello world!", // English
+ " Hello world! ", // English with space before/after
+ " Hello world? Foo bar!", // English
+ "Jedovatou mambu objevila žena v zahrádkářské kolonii.", // Czech
+ "Việt Nam: Nhất thể hóa sẽ khác Trung Quốc?", // Vietnamese
+ "Σοβαρές ενστάσεις Κομισιόν για τον προϋπολογισμό της Ιταλίας", // Greek
+ "Решение Индии о покупке российских С-400 расценили как вызов США", // Russian
+ "הרופא שהציל נשים והנערה ששועבדה ע", // Hebrew,
+ "ترامب للملك سلمان: أنا جاد للغاية.. عليك دفع المزيد", // Arabic
+ "भारत की एस 400 मिसाइल के मुकाबले पाक की थाड, जानें कौन कितना ताकतवर", // Hindi
+ "ரெட் அலர்ட் எச்சரிக்கை; புதுச்சேரியில் நாளை அரசு விடுமுறை!", // Tamil
+ "'ఉత్తర్వులు అందే వరకు ఓటర్ల తుది జాబితాను వెబ్‌సైట్లో పెట్టవద్దు'", // Telugu
+ "台北》抹黑柯P失敗?朱學恒酸:姚文智氣pupu嗆大老闆", // Chinese
+ "วัดไทรตีระฆังเบาลงช่วงเข้าพรรษา เจ้าอาวาสเผยคนร้องเรียนรับผลกรรมแล้ว", // Thai
+ "九州北部の一部が暴風域に入りました(日直予報士 2018年10月06日) - 日本気象協会 tenki.jp", // Japanese
+ "법원 “다스 지분 처분권·수익권 모두 MB가 보유”", // Korean
+ ]) {
+ let segments = [];
+ for (const v of seg.segment(text)) {
+ assert.sameValue("string", typeof v.segment);
+ assert.sameValue(true, v.segment.length > 0, "length > 0");
+
+ assert.sameValue("number", typeof v.index);
+ assert.sameValue(true, v.index >= 0, "index >= 0");
+ assert.sameValue(true, v.index < text.length, "index < input.length");
+
+ assert.sameValue("string", typeof v.input);
+ assert.sameValue(text, v.input);
+
+ assert.sameValue(undefined, v.isWordLike);
+ assert.sameValue(false, v.hasOwnProperty("isWordLike"));
+
+ assert.sameValue(text.slice(v.index, v.index + v.segment.length), v.segment);
+ assert.compareArray(Object.getOwnPropertyNames(v), ["segment", "index", "input"]);
+
+ segments.push(v.segment);
+ }
+ assert.sameValue(text, segments.join(''));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-tostring.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-tostring.js
new file mode 100644
index 0000000000..433cd9ad44
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-tostring.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors, Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Verifies the string coercion in the "segment" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+
+ 3. Let string be ? ToString(string).
+features: [Intl.Segmenter]
+---*/
+
+const tests = [
+ [[], "undefined"],
+ [[undefined], "undefined"],
+ [[null], "null"],
+ [[true], "true"],
+ [[false], "false"],
+ [[12], "12"],
+ [[1.23], "1.23"],
+ [[["a", "b"]], "a"],
+ [[{}], "["], // "[object Object]"
+];
+
+const segmenter = new Intl.Segmenter("en", { "granularity": "word" });
+for (const [args, expected] of tests) {
+ const segments = segmenter.segment(...args);
+ const actual = [...segments][0].segment;
+ assert.sameValue(actual, expected, `Expected segment "${expected}", found "${actual}" for arguments ${args}`);
+}
+
+const symbol = Symbol();
+assert.throws(TypeError, () => segmenter.segment(symbol));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-word-iterable.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-word-iterable.js
new file mode 100644
index 0000000000..af7b9598d5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/segment-word-iterable.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Copyright 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-Intl.Segmenter.prototype.segment
+description: Verifies the behavior for the "segment" function of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype.segment( string )
+includes: [compareArray.js]
+features: [Intl.Segmenter]
+---*/
+
+const seg = new Intl.Segmenter([], {granularity: "word"})
+for (const text of [
+ "Hello world!", // English
+ " Hello world! ", // English with space before/after
+ " Hello world? Foo bar!", // English
+ "Jedovatou mambu objevila žena v zahrádkářské kolonii.", // Czech
+ "Việt Nam: Nhất thể hóa sẽ khác Trung Quốc?", // Vietnamese
+ "Σοβαρές ενστάσεις Κομισιόν για τον προϋπολογισμό της Ιταλίας", // Greek
+ "Решение Индии о покупке российских С-400 расценили как вызов США", // Russian
+ "הרופא שהציל נשים והנערה ששועבדה ע", // Hebrew,
+ "ترامب للملك سلمان: أنا جاد للغاية.. عليك دفع المزيد", // Arabic
+ "भारत की एस 400 मिसाइल के मुकाबले पाक की थाड, जानें कौन कितना ताकतवर", // Hindi
+ "ரெட் அலர்ட் எச்சரிக்கை; புதுச்சேரியில் நாளை அரசு விடுமுறை!", // Tamil
+ "'ఉత్తర్వులు అందే వరకు ఓటర్ల తుది జాబితాను వెబ్‌సైట్లో పెట్టవద్దు'", // Telugu
+ "台北》抹黑柯P失敗?朱學恒酸:姚文智氣pupu嗆大老闆", // Chinese
+ "วัดไทรตีระฆังเบาลงช่วงเข้าพรรษา เจ้าอาวาสเผยคนร้องเรียนรับผลกรรมแล้ว", // Thai
+ "九州北部の一部が暴風域に入りました(日直予報士 2018年10月06日) - 日本気象協会 tenki.jp", // Japanese
+ "법원 “다스 지분 처분권·수익권 모두 MB가 보유”", // Korean
+ ]) {
+ let segments = [];
+ for (const v of seg.segment(text)) {
+ assert.sameValue("string", typeof v.segment);
+ assert.sameValue(true, v.segment.length > 0, "length > 0");
+
+ assert.sameValue("number", typeof v.index);
+ assert.sameValue(true, v.index >= 0, "index >= 0");
+ assert.sameValue(true, v.index < text.length, "index < input.length");
+
+ assert.sameValue("string", typeof v.input);
+ assert.sameValue(text, v.input);
+
+ assert.sameValue("boolean", typeof v.isWordLike);
+ assert.sameValue(true, v.hasOwnProperty("isWordLike"));
+
+ assert.sameValue(text.slice(v.index, v.index + v.segment.length), v.segment);
+ assert.compareArray(Object.getOwnPropertyNames(v), ["segment", "index", "input", "isWordLike"]);
+
+ segments.push(v.segment);
+ }
+ assert.sameValue(text, segments.join(''));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/segment/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/segment/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/segment/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/browser.js b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/browser.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/shell.js b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/shell.js
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toString.js b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toString.js
new file mode 100644
index 0000000000..b2bb14f326
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toString.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.Segmenter.prototype-@@tostringtag
+description: >
+ Checks Object.prototype.toString with Intl.Segmenter objects.
+info: |
+ Intl.Segmenter.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.Segmenter".
+features: [Intl.Segmenter]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(Intl.Segmenter.prototype), "[object Intl.Segmenter]");
+assert.sameValue(Object.prototype.toString.call(new Intl.Segmenter()), "[object Intl.Segmenter]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toStringTag.js b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toStringTag.js
new file mode 100644
index 0000000000..a5ce6a1c39
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/prototype/toStringTag/toStringTag.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!Intl.Segmenter) -- Intl.Segmenter is not enabled unconditionally
+// Copyright 2018 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-intl.Segmenter.prototype-@@tostringtag
+description: >
+ Checks the @@toStringTag property of the Segmenter prototype object.
+info: |
+ Intl.Segmenter.prototype[ @@toStringTag ]
+
+ The initial value of the @@toStringTag property is the string value "Intl.Segmenter".
+
+ This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Intl.Segmenter, Symbol.toStringTag]
+---*/
+
+verifyProperty(Intl.Segmenter.prototype, Symbol.toStringTag, {
+ value: "Intl.Segmenter",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Segmenter/shell.js b/js/src/tests/test262/intl402/Segmenter/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Segmenter/shell.js
diff --git a/js/src/tests/test262/intl402/String/browser.js b/js/src/tests/test262/intl402/String/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/browser.js
diff --git a/js/src/tests/test262/intl402/String/prototype/browser.js b/js/src/tests/test262/intl402/String/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/browser.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/browser.js
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/builtin.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/builtin.js
new file mode 100644
index 0000000000..ea9820bab9
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/builtin.js
@@ -0,0 +1,30 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_L15
+description: >
+ Tests that String.prototype.localeCompare meets the requirements
+ for built-in objects defined by the introduction of chapter 17 of
+ the ECMAScript Language Specification.
+author: Norbert Lindenberg
+includes: [isConstructor.js]
+features: [Reflect.construct]
+---*/
+
+assert.sameValue(Object.prototype.toString.call(String.prototype.localeCompare), "[object Function]",
+ "The [[Class]] internal property of a built-in function must be " +
+ "\"Function\".");
+
+assert(Object.isExtensible(String.prototype.localeCompare),
+ "Built-in objects must be extensible.");
+
+assert.sameValue(Object.getPrototypeOf(String.prototype.localeCompare), Function.prototype);
+
+assert.sameValue(String.prototype.localeCompare.hasOwnProperty("prototype"), false,
+ "Built-in functions that aren't constructors must not have a prototype property.");
+
+assert.sameValue(isConstructor(String.prototype.localeCompare), false,
+ "Built-in functions don't implement [[Construct]] unless explicitly specified.");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/default-options-object-prototype.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/default-options-object-prototype.js
new file mode 100644
index 0000000000..dac753245b
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/default-options-object-prototype.js
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Daniel Ehrenberg. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-initializecollator
+description: >
+ Monkey-patching Object.prototype does not change the default
+ options for Collator as a null prototype is used.
+info: |
+ InitializeCollator ( collator, locales, options )
+
+ 1. If _options_ is *undefined*, then
+ 1. Let _options_ be ObjectCreate(*null*).
+---*/
+
+if (new Intl.Collator("en").resolvedOptions().locale === "en") {
+ Object.prototype.sensitivity = "base";
+ assert.sameValue("a".localeCompare("A"), -1);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/length.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/length.js
new file mode 100644
index 0000000000..8fb28c2328
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/length.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-String.prototype.localeCompare
+description: >
+ String.prototype.localeCompare.length is 1.
+info: |
+ String.prototype.localeCompare ( that [ , locales [ , options ] ] )
+
+ 17 ECMAScript Standard Built-in Objects:
+
+ Every built-in function object, including constructors, has a length
+ property whose value is an integer. Unless otherwise specified, this
+ value is equal to the largest number of named arguments shown in the
+ subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which
+ are shown using the form «...name») are not included in the default
+ argument count.
+ Unless otherwise specified, the length property of a built-in function
+ object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+ [[Configurable]]: true }.
+
+includes: [propertyHelper.js]
+---*/
+
+verifyProperty(String.prototype.localeCompare, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/missing-arguments-coerced-to-undefined.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/missing-arguments-coerced-to-undefined.js
new file mode 100644
index 0000000000..2b80e84a89
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/missing-arguments-coerced-to-undefined.js
@@ -0,0 +1,21 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_3_2
+description: >
+ Tests that String.prototype.localeCompare treats a missing "that"
+ argument, undefined, and "undefined" as equivalent.
+author: Norbert Lindenberg
+---*/
+
+var thisValues = ["a", "t", "u", "undefined", "UNDEFINED", "nicht definiert", "xyz", "未定义"];
+
+var i;
+for (i = 0; i < thisValues.length; i++) {
+ var thisValue = thisValues[i];
+ assert.sameValue(thisValue.localeCompare(), thisValue.localeCompare(undefined), "String.prototype.localeCompare does not treat missing 'that' argument as undefined.");
+ assert.sameValue(thisValue.localeCompare(undefined), thisValue.localeCompare("undefined"), "String.prototype.localeCompare does not treat undefined 'that' argument as \"undefined\".");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/return-abrupt-this-value.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/return-abrupt-this-value.js
new file mode 100644
index 0000000000..21de34a98d
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/return-abrupt-this-value.js
@@ -0,0 +1,20 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_1
+description: >
+ Tests that localeCompare rejects values that can't be coerced to
+ an object.
+author: Norbert Lindenberg
+---*/
+
+var invalidValues = [undefined, null];
+
+invalidValues.forEach(function (value) {
+ assert.throws(TypeError, function() {
+ var result = String.prototype.localeCompare.call(value, "");
+ }, "String.prototype.localeCompare did not reject this = " + value + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/returns-same-results-as-Collator.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/returns-same-results-as-Collator.js
new file mode 100644
index 0000000000..c93530777d
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/returns-same-results-as-Collator.js
@@ -0,0 +1,32 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_7
+description: >
+ Tests that localeCompare produces the same results as
+ Intl.Collator.
+author: Norbert Lindenberg
+includes: [compareArray.js]
+---*/
+
+var strings = ["d", "O", "od", "oe", "of", "ö", "o\u0308", "X", "y", "Z", "Z.", "𠮷野家", "吉野家", "!A", "A", "b", "C"];
+var locales = [undefined, ["de"], ["de-u-co-phonebk"], ["en"], ["ja"], ["sv"]];
+var options = [
+ undefined,
+ {usage: "search"},
+ {sensitivity: "base", ignorePunctuation: true}
+];
+
+locales.forEach(function (locales) {
+ options.forEach(function (options) {
+ var referenceCollator = new Intl.Collator(locales, options);
+ var referenceSorted = strings.slice().sort(referenceCollator.compare);
+
+ strings.sort(function (a, b) { return a.localeCompare(b, locales, options); });
+ assert.compareArray(strings, referenceSorted,
+ "(Testing with locales " + locales + "; options " + JSON.stringify(options) + ".)");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/shell.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/shell.js
new file mode 100644
index 0000000000..eda1477282
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/shell.js
@@ -0,0 +1,24 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/taint-Intl-Collator.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/taint-Intl-Collator.js
new file mode 100644
index 0000000000..2955b6a8f0
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/taint-Intl-Collator.js
@@ -0,0 +1,16 @@
+// Copyright 2013 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_6_2
+description: >
+ Tests that String.prototype.localeCompare uses the standard
+ built-in Intl.Collator constructor.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintDataProperty(Intl, "Collator");
+"a".localeCompare("b");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/that-arg-coerced-to-string.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/that-arg-coerced-to-string.js
new file mode 100644
index 0000000000..8de5a8129c
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/that-arg-coerced-to-string.js
@@ -0,0 +1,22 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_3_1
+description: Tests that localeCompare coerces that to a string.
+author: Norbert Lindenberg
+---*/
+
+var thisValues = ["true", "5", "hello", "good bye"];
+var thatValues = [true, 5, "hello", {toString: function () { return "good bye"; }}];
+
+var i;
+for (i = 0; i < thisValues.length; i++) {
+ var j;
+ for (j = 0; j < thatValues.length; j++) {
+ var result = String.prototype.localeCompare.call(thisValues[i], thatValues[j]);
+ assert.sameValue((result === 0), (i === j), "localeCompare treats " + thisValues[i] + " and " + thatValues[j] + " as " + (result === 0 ? "equal" : "different") + ".");
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/this-value-coerced-to-string.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/this-value-coerced-to-string.js
new file mode 100644
index 0000000000..b8c999059e
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/this-value-coerced-to-string.js
@@ -0,0 +1,22 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_2
+description: Tests that localeCompare coerces this to a string.
+author: Norbert Lindenberg
+---*/
+
+var thisValues = [true, 5, "hello", {toString: function () { return "good bye"; }}];
+var thatValues = ["true", "5", "hello", "good bye"];
+
+var i;
+for (i = 0; i < thisValues.length; i++) {
+ var j;
+ for (j = 0; j < thatValues.length; j++) {
+ var result = String.prototype.localeCompare.call(thisValues[i], thatValues[j]);
+ assert.sameValue((result === 0), (i === j), "localeCompare treats " + thisValues[i] + " and " + thatValues[j] + " as " + (result === 0 ? "equal" : "different") + ".");
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/localeCompare/throws-same-exceptions-as-Collator.js b/js/src/tests/test262/intl402/String/prototype/localeCompare/throws-same-exceptions-as-Collator.js
new file mode 100644
index 0000000000..3910154616
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/localeCompare/throws-same-exceptions-as-Collator.js
@@ -0,0 +1,47 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 13.1.1_6_1
+description: >
+ Tests that String.prototype.localeCompare throws the same
+ exceptions as Intl.Collator.
+author: Norbert Lindenberg
+---*/
+
+var locales = [null, [NaN], ["i"], ["de_DE"]];
+var options = [
+ {localeMatcher: null},
+ {usage: "invalid"},
+ {sensitivity: "invalid"}
+];
+
+locales.forEach(function (locales) {
+ var referenceError, error;
+ try {
+ var collator = new Intl.Collator(locales);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.Collator for locales " + locales + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = "".localeCompare("", locales);
+ }, "String.prototype.localeCompare didn't throw exception for locales " + locales + ".");
+});
+
+options.forEach(function (options) {
+ var referenceError, error;
+ try {
+ var collator = new Intl.Collator([], options);
+ } catch (e) {
+ referenceError = e;
+ }
+ assert.notSameValue(referenceError, undefined, "Internal error: Expected exception was not thrown by Intl.Collator for options " + JSON.stringify(options) + ".");
+
+ assert.throws(referenceError.constructor, function() {
+ var result = "".localeCompare("", [], options);
+ }, "String.prototype.localeCompare didn't throw exception for options " + JSON.stringify(options) + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/shell.js b/js/src/tests/test262/intl402/String/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/browser.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/browser.js
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js
new file mode 100644
index 0000000000..aa273e7a50
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js
@@ -0,0 +1,17 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleLowerCase supports mappings defined in SpecialCasings
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.20
+---*/
+
+// Locale-sensitive for Turkish and Azeri.
+assert.sameValue("\u0130".toLocaleLowerCase("und"), "\u0069\u0307", "LATIN CAPITAL LETTER I WITH DOT ABOVE");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/shell.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/shell.js
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js
new file mode 100644
index 0000000000..82989e6874
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js
@@ -0,0 +1,87 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleLowerCase supports language-sensitive mappings defined in SpecialCasings (Azeri)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.20
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Azeri).
+
+// LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) changed to LATIN SMALL LETTER I when lowercasing.
+assert.sameValue(
+ "\u0130".toLocaleLowerCase("az"),
+ "i",
+ "LATIN CAPITAL LETTER I WITH DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) removed after LATIN CAPITAL LETTER I when lowercasing.
+// - COMBINING DOT BELOW (U+0323), combining class 220 (Below)
+// - PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE (U+101FD = D800 DDFD), combining class 220 (Below)
+assert.sameValue(
+ "I\u0307".toLocaleLowerCase("az"),
+ "i",
+ "LATIN CAPITAL LETTER I followed by COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\u0323\u0307".toLocaleLowerCase("az"),
+ "i\u0323",
+ "LATIN CAPITAL LETTER I followed by COMBINING DOT BELOW, COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\uD800\uDDFD\u0307".toLocaleLowerCase("az"),
+ "i\uD800\uDDFD",
+ "LATIN CAPITAL LETTER I followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, COMBINING DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) not removed when character is preceded by a character of combining class 0.
+assert.sameValue(
+ "IA\u0307".toLocaleLowerCase("az"),
+ "\u0131a\u0307",
+ "LATIN CAPITAL LETTER I followed by LATIN CAPITAL LETTER A, COMBINING DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) not removed when character is preceded by a character of combining class 230.
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "I\u0300\u0307".toLocaleLowerCase("az"),
+ "\u0131\u0300\u0307",
+ "LATIN CAPITAL LETTER I followed by COMBINING GRAVE ACCENT, COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\uD834\uDD85\u0307".toLocaleLowerCase("az"),
+ "\u0131\uD834\uDD85\u0307",
+ "LATIN CAPITAL LETTER I followed by MUSICAL SYMBOL COMBINING DOIT, COMBINING DOT ABOVE"
+);
+
+
+// LATIN CAPITAL LETTER I changed to LATIN SMALL LETTER DOTLESS I (U+0131) when lowercasing.
+assert.sameValue(
+ "I".toLocaleLowerCase("az"),
+ "\u0131",
+ "LATIN CAPITAL LETTER I"
+);
+
+
+// No changes when lowercasing LATIN SMALL LETTER I and LATIN SMALL LETTER DOTLESS I (U+0131).
+assert.sameValue(
+ "i".toLocaleLowerCase("az"),
+ "i",
+ "LATIN SMALL LETTER I"
+);
+assert.sameValue(
+ "\u0131".toLocaleLowerCase("az"),
+ "\u0131",
+ "LATIN SMALL LETTER DOTLESS I"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js
new file mode 100644
index 0000000000..73f38e190c
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js
@@ -0,0 +1,195 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleLowerCase supports language-sensitive mappings defined in SpecialCasings (Lithuanian)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.20
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Lithuanian).
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character directly followed by character of combining class 230 (Above).
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+assert.sameValue(
+ "I\u0300".toLocaleLowerCase("lt"),
+ "i\u0307\u0300",
+ "LATIN CAPITAL LETTER I followed by COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "J\u0300".toLocaleLowerCase("lt"),
+ "j\u0307\u0300",
+ "LATIN CAPITAL LETTER J followed by COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "\u012E\u0300".toLocaleLowerCase("lt"),
+ "\u012F\u0307\u0300",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by COMBINING GRAVE ACCENT"
+);
+
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character directly followed by character of combining class 230 (Above).
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "I\uD834\uDD85".toLocaleLowerCase("lt"),
+ "i\u0307\uD834\uDD85",
+ "LATIN CAPITAL LETTER I followed by MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "J\uD834\uDD85".toLocaleLowerCase("lt"),
+ "j\u0307\uD834\uDD85",
+ "LATIN CAPITAL LETTER J followed by MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "\u012E\uD834\uDD85".toLocaleLowerCase("lt"),
+ "\u012F\u0307\uD834\uDD85",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by MUSICAL SYMBOL COMBINING DOIT"
+);
+
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character not directly followed by character of combining class 230 (Above).
+// - COMBINING RING BELOW (U+0325), combining class 220 (Below)
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+assert.sameValue(
+ "I\u0325\u0300".toLocaleLowerCase("lt"),
+ "i\u0307\u0325\u0300",
+ "LATIN CAPITAL LETTER I followed by COMBINING RING BELOW, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "J\u0325\u0300".toLocaleLowerCase("lt"),
+ "j\u0307\u0325\u0300",
+ "LATIN CAPITAL LETTER J followed by COMBINING RING BELOW, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "\u012E\u0325\u0300".toLocaleLowerCase("lt"),
+ "\u012F\u0307\u0325\u0300",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by COMBINING RING BELOW, COMBINING GRAVE ACCENT"
+);
+
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character not directly followed by character of combining class 230 (Above).
+// - PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE (U+101FD, D800 DDFD), combining class 220 (Below)
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+assert.sameValue(
+ "I\uD800\uDDFD\u0300".toLocaleLowerCase("lt"),
+ "i\u0307\uD800\uDDFD\u0300",
+ "LATIN CAPITAL LETTER I followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "J\uD800\uDDFD\u0300".toLocaleLowerCase("lt"),
+ "j\u0307\uD800\uDDFD\u0300",
+ "LATIN CAPITAL LETTER J followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "\u012E\uD800\uDDFD\u0300".toLocaleLowerCase("lt"),
+ "\u012F\u0307\uD800\uDDFD\u0300",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, COMBINING GRAVE ACCENT"
+);
+
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character not directly followed by character of combining class 230 (Above).
+// - COMBINING RING BELOW (U+0325), combining class 220 (Below)
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "I\u0325\uD834\uDD85".toLocaleLowerCase("lt"),
+ "i\u0307\u0325\uD834\uDD85",
+ "LATIN CAPITAL LETTER I followed by COMBINING RING BELOW, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "J\u0325\uD834\uDD85".toLocaleLowerCase("lt"),
+ "j\u0307\u0325\uD834\uDD85",
+ "LATIN CAPITAL LETTER J followed by COMBINING RING BELOW, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "\u012E\u0325\uD834\uDD85".toLocaleLowerCase("lt"),
+ "\u012F\u0307\u0325\uD834\uDD85",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by COMBINING RING BELOW, MUSICAL SYMBOL COMBINING DOIT"
+);
+
+
+// COMBINING DOT ABOVE added when lowercasing capital I, J and I WITH OGONEK (U+012E).
+// Character not directly followed by character of combining class 230 (Above).
+// - PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE (U+101FD, D800 DDFD), combining class 220 (Below)
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "I\uD800\uDDFD\uD834\uDD85".toLocaleLowerCase("lt"),
+ "i\u0307\uD800\uDDFD\uD834\uDD85",
+ "LATIN CAPITAL LETTER I followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "J\uD800\uDDFD\uD834\uDD85".toLocaleLowerCase("lt"),
+ "j\u0307\uD800\uDDFD\uD834\uDD85",
+ "LATIN CAPITAL LETTER J followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "\u012E\uD800\uDDFD\uD834\uDD85".toLocaleLowerCase("lt"),
+ "\u012F\u0307\uD800\uDDFD\uD834\uDD85",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, MUSICAL SYMBOL COMBINING DOIT"
+);
+
+
+// COMBINING DOT ABOVE not added when character is followed by a character of combining class 0.
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+assert.sameValue(
+ "IA\u0300".toLocaleLowerCase("lt"),
+ "ia\u0300",
+ "LATIN CAPITAL LETTER I followed by LATIN CAPITAL LETTER A, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "JA\u0300".toLocaleLowerCase("lt"),
+ "ja\u0300",
+ "LATIN CAPITAL LETTER J followed by LATIN CAPITAL LETTER A, COMBINING GRAVE ACCENT"
+);
+assert.sameValue(
+ "\u012EA\u0300".toLocaleLowerCase("lt"),
+ "\u012Fa\u0300",
+ "LATIN CAPITAL LETTER I WITH OGONEK followed by LATIN CAPITAL LETTER A, COMBINING GRAVE ACCENT"
+);
+
+
+// COMBINING DOT ABOVE not added when character is followed by a character of combining class 0.
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "IA\uD834\uDD85".toLocaleLowerCase("lt"),
+ "ia\uD834\uDD85",
+ "LATIN CAPITAL LETTER I followed by LATIN CAPITAL LETTER A, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "JA\uD834\uDD85".toLocaleLowerCase("lt"),
+ "ja\uD834\uDD85",
+ "LATIN CAPITAL LETTER J followed by LATIN CAPITAL LETTER A, MUSICAL SYMBOL COMBINING DOIT"
+);
+assert.sameValue(
+ "\u012EA\uD834\uDD85".toLocaleLowerCase("lt"),
+ "\u012Fa\uD834\uDD85",
+ "LATIN CAPITAL LETTER I WITH OGONEK (U+012E) followed by LATIN CAPITAL LETTER A, MUSICAL SYMBOL COMBINING DOIT"
+);
+
+
+// Precomposed characters with accents above.
+assert.sameValue(
+ "\u00CC".toLocaleLowerCase("lt"),
+ "\u0069\u0307\u0300",
+ "LATIN CAPITAL LETTER I WITH GRAVE"
+);
+assert.sameValue(
+ "\u00CD".toLocaleLowerCase("lt"),
+ "\u0069\u0307\u0301",
+ "LATIN CAPITAL LETTER I WITH ACUTE"
+);
+assert.sameValue(
+ "\u0128".toLocaleLowerCase("lt"),
+ "\u0069\u0307\u0303",
+ "LATIN CAPITAL LETTER I WITH TILDE"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js
new file mode 100644
index 0000000000..bbd4578243
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js
@@ -0,0 +1,87 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleLowerCase supports language-sensitive mappings defined in SpecialCasings (Turkish)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.20
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Turkish).
+
+// LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) changed to LATIN SMALL LETTER I when lowercasing.
+assert.sameValue(
+ "\u0130".toLocaleLowerCase("tr"),
+ "i",
+ "LATIN CAPITAL LETTER I WITH DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) removed after LATIN CAPITAL LETTER I when lowercasing.
+// - COMBINING DOT BELOW (U+0323), combining class 220 (Below)
+// - PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE (U+101FD = D800 DDFD), combining class 220 (Below)
+assert.sameValue(
+ "I\u0307".toLocaleLowerCase("tr"),
+ "i",
+ "LATIN CAPITAL LETTER I followed by COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\u0323\u0307".toLocaleLowerCase("tr"),
+ "i\u0323",
+ "LATIN CAPITAL LETTER I followed by COMBINING DOT BELOW, COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\uD800\uDDFD\u0307".toLocaleLowerCase("tr"),
+ "i\uD800\uDDFD",
+ "LATIN CAPITAL LETTER I followed by PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE, COMBINING DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) not removed when character is preceded by a character of combining class 0.
+assert.sameValue(
+ "IA\u0307".toLocaleLowerCase("tr"),
+ "\u0131a\u0307",
+ "LATIN CAPITAL LETTER I followed by LATIN CAPITAL LETTER A, COMBINING DOT ABOVE"
+);
+
+
+// COMBINING DOT ABOVE (U+0307) not removed when character is preceded by a character of combining class 230.
+// - COMBINING GRAVE ACCENT (U+0300), combining class 230 (Above)
+// - MUSICAL SYMBOL COMBINING DOIT (U+1D185, D834 DD85), combining class 230 (Above)
+assert.sameValue(
+ "I\u0300\u0307".toLocaleLowerCase("tr"),
+ "\u0131\u0300\u0307",
+ "LATIN CAPITAL LETTER I followed by COMBINING GRAVE ACCENT, COMBINING DOT ABOVE"
+);
+assert.sameValue(
+ "I\uD834\uDD85\u0307".toLocaleLowerCase("tr"),
+ "\u0131\uD834\uDD85\u0307",
+ "LATIN CAPITAL LETTER I followed by MUSICAL SYMBOL COMBINING DOIT, COMBINING DOT ABOVE"
+);
+
+
+// LATIN CAPITAL LETTER I changed to LATIN SMALL LETTER DOTLESS I (U+0131) when lowercasing.
+assert.sameValue(
+ "I".toLocaleLowerCase("tr"),
+ "\u0131",
+ "LATIN CAPITAL LETTER I"
+);
+
+
+// No changes when lowercasing LATIN SMALL LETTER I and LATIN SMALL LETTER DOTLESS I (U+0131).
+assert.sameValue(
+ "i".toLocaleLowerCase("tr"),
+ "i",
+ "LATIN SMALL LETTER I"
+);
+assert.sameValue(
+ "\u0131".toLocaleLowerCase("tr"),
+ "\u0131",
+ "LATIN SMALL LETTER DOTLESS I"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/browser.js b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/browser.js
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/shell.js b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/shell.js
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Azeri.js b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Azeri.js
new file mode 100644
index 0000000000..8b5021555a
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Azeri.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleUpperCase supports language-sensitive mappings defined in SpecialCasings (Azeri)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.21
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Azeri).
+
+// LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) not changed when uppercasing.
+assert.sameValue(
+ "\u0130".toLocaleUpperCase("az"),
+ "\u0130",
+ "LATIN CAPITAL LETTER I WITH DOT ABOVE"
+);
+
+
+// LATIN CAPITAL LETTER I not changed when uppercasing.
+assert.sameValue(
+ "I".toLocaleUpperCase("az"),
+ "I",
+ "LATIN CAPITAL LETTER I"
+);
+
+
+// LATIN SMALL LETTER I changed to LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) when uppercasing.
+assert.sameValue(
+ "i".toLocaleUpperCase("az"),
+ "\u0130",
+ "LATIN SMALL LETTER I"
+);
+
+
+// LATIN SMALL LETTER DOTLESS I (U+0131) changed to LATIN CAPITAL LETTER I when uppercasing.
+assert.sameValue(
+ "\u0131".toLocaleUpperCase("az"),
+ "I",
+ "LATIN SMALL LETTER DOTLESS I"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Lithuanian.js b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Lithuanian.js
new file mode 100644
index 0000000000..2dfefd9851
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Lithuanian.js
@@ -0,0 +1,115 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleUpperCase supports language-sensitive mappings defined in SpecialCasings (Lithuanian)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.21
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Lithuanian).
+
+// COMBINING DOT ABOVE (U+0307) not removed when uppercasing capital I and J.
+assert.sameValue(
+ "I\u0307".toLocaleUpperCase("lt"),
+ "I\u0307",
+ "COMBINING DOT ABOVE preceded by LATIN CAPITAL LETTER I"
+);
+assert.sameValue(
+ "J\u0307".toLocaleUpperCase("lt"),
+ "J\u0307",
+ "COMBINING DOT ABOVE preceded by LATIN CAPITAL LETTER J"
+);
+
+
+// Code points with Soft_Dotted property (Unicode 5.1, PropList.txt)
+var softDotted = [
+ "\u0069", "\u006A", // LATIN SMALL LETTER I..LATIN SMALL LETTER J
+ "\u012F", // LATIN SMALL LETTER I WITH OGONEK
+ "\u0249", // LATIN SMALL LETTER J WITH STROKE
+ "\u0268", // LATIN SMALL LETTER I WITH STROKE
+ "\u029D", // LATIN SMALL LETTER J WITH CROSSED-TAIL
+ "\u02B2", // MODIFIER LETTER SMALL J
+ "\u03F3", // GREEK LETTER YOT
+ "\u0456", // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ "\u0458", // CYRILLIC SMALL LETTER JE
+ "\u1D62", // LATIN SUBSCRIPT SMALL LETTER I
+ "\u1D96", // LATIN SMALL LETTER I WITH RETROFLEX HOOK
+ "\u1DA4", // MODIFIER LETTER SMALL I WITH STROKE
+ "\u1DA8", // MODIFIER LETTER SMALL J WITH CROSSED-TAIL
+ "\u1E2D", // LATIN SMALL LETTER I WITH TILDE BELOW
+ "\u1ECB", // LATIN SMALL LETTER I WITH DOT BELOW
+ "\u2071", // SUPERSCRIPT LATIN SMALL LETTER I
+ "\u2148", "\u2149", // DOUBLE-STRUCK ITALIC SMALL I..DOUBLE-STRUCK ITALIC SMALL J
+ "\u2C7C", // LATIN SUBSCRIPT SMALL LETTER J
+ "\uD835\uDC22", "\uD835\uDC23", // MATHEMATICAL BOLD SMALL I..MATHEMATICAL BOLD SMALL J
+ "\uD835\uDC56", "\uD835\uDC57", // MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL J
+ "\uD835\uDC8A", "\uD835\uDC8B", // MATHEMATICAL BOLD ITALIC SMALL I..MATHEMATICAL BOLD ITALIC SMALL J
+ "\uD835\uDCBE", "\uD835\uDCBF", // MATHEMATICAL SCRIPT SMALL I..MATHEMATICAL SCRIPT SMALL J
+ "\uD835\uDCF2", "\uD835\uDCF3", // MATHEMATICAL BOLD SCRIPT SMALL I..MATHEMATICAL BOLD SCRIPT SMALL J
+ "\uD835\uDD26", "\uD835\uDD27", // MATHEMATICAL FRAKTUR SMALL I..MATHEMATICAL FRAKTUR SMALL J
+ "\uD835\uDD5A", "\uD835\uDD5B", // MATHEMATICAL DOUBLE-STRUCK SMALL I..MATHEMATICAL DOUBLE-STRUCK SMALL J
+ "\uD835\uDD8E", "\uD835\uDD8F", // MATHEMATICAL BOLD FRAKTUR SMALL I..MATHEMATICAL BOLD FRAKTUR SMALL J
+ "\uD835\uDDC2", "\uD835\uDDC3", // MATHEMATICAL SANS-SERIF SMALL I..MATHEMATICAL SANS-SERIF SMALL J
+ "\uD835\uDDF6", "\uD835\uDDF7", // MATHEMATICAL SANS-SERIF BOLD SMALL I..MATHEMATICAL SANS-SERIF BOLD SMALL J
+ "\uD835\uDE2A", "\uD835\uDE2B", // MATHEMATICAL SANS-SERIF ITALIC SMALL I..MATHEMATICAL SANS-SERIF ITALIC SMALL J
+ "\uD835\uDE5E", "\uD835\uDE5F", // MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J
+ "\uD835\uDE92", "\uD835\uDE93", // MATHEMATICAL MONOSPACE SMALL I..MATHEMATICAL MONOSPACE SMALL J
+];
+assert.sameValue(softDotted.length, 46, "Total code points with Soft_Dotted property");
+
+function charInfo(ch) {
+ function hexString(n) {
+ var s = n.toString(16).toUpperCase();
+ return "0000".slice(s.length) + s;
+ }
+
+ if (ch.length === 1) {
+ return "U+" + hexString(ch.charCodeAt(0));
+ }
+ var high = ch.charCodeAt(0);
+ var low = ch.charCodeAt(1);
+ var codePoint = ((high << 10) + low) + (0x10000 - (0xD800 << 10) - 0xDC00);
+ return "U+" + hexString(codePoint) + " = " + hexString(high) + " " + hexString(low);
+}
+
+
+// COMBINING DOT ABOVE (U+0307) removed when preceded by Soft_Dotted.
+// Character directly preceded by Soft_Dotted.
+for (var i = 0; i < softDotted.length; ++i) {
+ assert.sameValue(
+ (softDotted[i] + "\u0307").toLocaleUpperCase("lt"),
+ softDotted[i].toLocaleUpperCase("und"),
+ "COMBINING DOT ABOVE preceded by Soft_Dotted (" + charInfo(softDotted[i]) + ")"
+ );
+}
+
+
+// COMBINING DOT ABOVE (U+0307) removed if preceded by Soft_Dotted.
+// Character not directly preceded by Soft_Dotted.
+// - COMBINING DOT BELOW (U+0323), combining class 220 (Below)
+for (var i = 0; i < softDotted.length; ++i) {
+ assert.sameValue(
+ (softDotted[i] + "\u0323\u0307").toLocaleUpperCase("lt"),
+ softDotted[i].toLocaleUpperCase("und") + "\u0323",
+ "COMBINING DOT ABOVE preceded by Soft_Dotted (" + charInfo(softDotted[i]) + "), COMBINING DOT BELOW"
+ );
+}
+
+
+// COMBINING DOT ABOVE removed if preceded by Soft_Dotted.
+// Character not directly preceded by Soft_Dotted.
+// - PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE (U+101FD = D800 DDFD), combining class 220 (Below)
+for (var i = 0; i < softDotted.length; ++i) {
+ assert.sameValue(
+ (softDotted[i] + "\uD800\uDDFD\u0307").toLocaleUpperCase("lt"),
+ softDotted[i].toLocaleUpperCase("und") + "\uD800\uDDFD",
+ "COMBINING DOT ABOVE preceded by Soft_Dotted (" + charInfo(softDotted[i]) + "), PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Turkish.js b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Turkish.js
new file mode 100644
index 0000000000..12597a407a
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/prototype/toLocaleUpperCase/special_casing_Turkish.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ Check if String.prototype.toLocaleUpperCase supports language-sensitive mappings defined in SpecialCasings (Turkish)
+info: |
+ The result must be derived according to the case mappings in the Unicode character database (this explicitly
+ includes not only the UnicodeData.txt file, but also the SpecialCasings.txt file that accompanies it).
+es5id: 15.5.4.16
+es6id: 21.1.3.21
+---*/
+
+// SpecialCasing.txt, conditional, language-sensitive mappings (Turkish).
+
+// LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) not changed when uppercasing.
+assert.sameValue(
+ "\u0130".toLocaleUpperCase("tr"),
+ "\u0130",
+ "LATIN CAPITAL LETTER I WITH DOT ABOVE"
+);
+
+
+// LATIN CAPITAL LETTER I not changed when uppercasing.
+assert.sameValue(
+ "I".toLocaleUpperCase("tr"),
+ "I",
+ "LATIN CAPITAL LETTER I"
+);
+
+
+// LATIN SMALL LETTER I changed to LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130) when uppercasing.
+assert.sameValue(
+ "i".toLocaleUpperCase("tr"),
+ "\u0130",
+ "LATIN SMALL LETTER I"
+);
+
+
+// LATIN SMALL LETTER DOTLESS I (U+0131) changed to LATIN CAPITAL LETTER I when uppercasing.
+assert.sameValue(
+ "\u0131".toLocaleUpperCase("tr"),
+ "I",
+ "LATIN SMALL LETTER DOTLESS I"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/String/shell.js b/js/src/tests/test262/intl402/String/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/String/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/Calendar/calendar-case-insensitive.js
new file mode 100644
index 0000000000..6fa399dd78
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/calendar-case-insensitive.js
@@ -0,0 +1,14 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar
+description: Calendar names are case-insensitive
+features: [Temporal]
+---*/
+
+const result = new Temporal.Calendar("jApAnEsE");
+assert.sameValue(result.toString(), "japanese", "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/from/basic.js b/js/src/tests/test262/intl402/Temporal/Calendar/from/basic.js
new file mode 100644
index 0000000000..196eea3ed5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/from/basic.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.from
+description: Support for non-ISO calendars in Calendar.from().
+features: [Temporal]
+---*/
+
+function test(item, id = item) {
+ const calendar = Temporal.Calendar.from(item);
+ assert(calendar instanceof Temporal.Calendar, `Calendar.from(${item}) is a calendar`);
+ assert.sameValue(calendar.id, id, `Calendar.from(${item}) has the correct ID`);
+}
+test("gregory");
+test("japanese");
+test("1994-11-05T08:15:30-05:00[u-ca=gregory]", "gregory");
+test("1994-11-05T13:15:30Z[u-ca=japanese]", "japanese");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/from/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/from/calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/Calendar/from/calendar-case-insensitive.js
new file mode 100644
index 0000000000..1fb9adda6c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/from/calendar-case-insensitive.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.from
+description: Calendar names are case-insensitive
+features: [Temporal]
+---*/
+
+const arg = "jApAnEsE";
+
+const result = Temporal.Calendar.from(arg);
+assert.sameValue(result.id, "japanese", "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/from/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/from/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/from/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/date-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/date-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..dd39ea7730
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/date-infinity-throws-rangeerror.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.dateadd
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const duration = new Temporal.Duration(1);
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => instance.dateAdd({ era: "ad", eraYear: inf, month: 5, day: 2, calendar: instance }, duration, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dateAdd({ era: "ad", eraYear: obj, month: 5, day: 2, calendar: instance }, duration, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateAdd/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..e2d06d22f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.datefromfields
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => instance.dateFromFields({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dateFromFields({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/one-of-era-erayear-undefined.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/one-of-era-erayear-undefined.js
new file mode 100644
index 0000000000..be0d4e8a61
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/one-of-era-erayear-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.datefromfields
+description: Throw a TypeError if only one of era/eraYear fields is present
+features: [Temporal]
+---*/
+
+const base = { year: 2000, month: 5, day: 2, era: 'ce' };
+const instance = new Temporal.Calendar('gregory');
+assert.throws(TypeError, () => {
+ instance.dateFromFields({ ...base });
+});
+
+const base2 = { year: 2000, month: 5, day: 2, eraYear: 1 };
+assert.throws(TypeError, () => {
+ instance.dateFromFields({ ...base2 });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/order-of-operations.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/order-of-operations.js
new file mode 100644
index 0000000000..87db045a8d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/order-of-operations.js
@@ -0,0 +1,79 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.datefromfields
+description: Properties on objects passed to dateFromFields() are accessed in the correct order
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const expected = [
+ "get fields.day",
+ "get fields.day.valueOf",
+ "call fields.day.valueOf",
+ "get fields.era",
+ "get fields.era.toString",
+ "call fields.era.toString",
+ "get fields.eraYear",
+ "get fields.eraYear.valueOf",
+ "call fields.eraYear.valueOf",
+ "get fields.month",
+ "get fields.month.valueOf",
+ "call fields.month.valueOf",
+ "get fields.monthCode",
+ "get fields.monthCode.toString",
+ "call fields.monthCode.toString",
+ "get fields.year",
+ "get fields.year.valueOf",
+ "call fields.year.valueOf",
+ "get options.overflow",
+ "get options.overflow.toString",
+ "call options.overflow.toString",
+];
+const actual = [];
+
+const instance = new Temporal.Calendar("gregory");
+
+const fields = {
+ era: "ce",
+ eraYear: 1.7,
+ year: 1.7,
+ month: 1.7,
+ monthCode: "M01",
+ day: 1.7,
+};
+const arg1 = new Proxy(fields, {
+ get(target, key) {
+ actual.push(`get fields.${key}`);
+ if (key === "calendar") return instance;
+ const result = target[key];
+ return TemporalHelpers.toPrimitiveObserver(actual, result, `fields.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has fields.${key}`);
+ return key in target;
+ },
+});
+
+const options = {
+ overflow: "reject",
+};
+const arg2 = new Proxy(options, {
+ get(target, key) {
+ actual.push(`get options.${key}`);
+ return TemporalHelpers.toPrimitiveObserver(actual, target[key], `options.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has options.${key}`);
+ return key in target;
+ },
+});
+
+const result = instance.dateFromFields(arg1, arg2);
+TemporalHelpers.assertPlainDate(result, 1, 1, "M01", 1, "date result", "ce", 1);
+assert.sameValue(result.getISOFields().calendar, "gregory", "calendar slot should store a string");
+assert.compareArray(actual, expected, "order of operations");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateFromFields/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/argument-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/argument-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..5f8b431d70
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/argument-infinity-throws-rangeerror.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.dateuntil
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const other = new Temporal.PlainDate(2001, 6, 3);
+const base = { era: "ad", month: 5, day: 2, calendar: instance };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.dateUntil({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => instance.dateUntil(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dateUntil({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dateUntil(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/until-across-lunisolar-leap-months.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/until-across-lunisolar-leap-months.js
new file mode 100644
index 0000000000..4114320ba6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/until-across-lunisolar-leap-months.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: dateUntil works as expected after a leap month in a lunisolar calendar
+esid: sec-temporal.calendar.prototype.dateuntil
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("chinese");
+
+// 2001 is a leap year in the Chinese calendar with a M04L leap month.
+// Therefore, month: 6 is M05 in 2001 but M06 in 2000 which is not a leap year.
+const one = Temporal.PlainDate.from({ year: 2000, month: 6, day: 1, calendar: 'chinese' });
+const two = Temporal.PlainDate.from({ year: 2001, month: 6, day: 1, calendar: 'chinese' });
+
+const expected = { years: 'P12M', months: 'P12M', weeks: 'P50W4D', days: 'P354D' };
+
+Object.entries(expected).forEach(([largestUnit, expectedResult]) => {
+ const actualResult = instance.dateUntil(one, two, { largestUnit });
+ assert.sameValue(actualResult.toString(), expectedResult);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/zero-length-duration-result.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/zero-length-duration-result.js
new file mode 100644
index 0000000000..b4aa45e979
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dateUntil/zero-length-duration-result.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The duration from a date to itself is a zero duration (PT0S)
+esid: sec-temporal.calendar.prototype.dateuntil
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const date = new Temporal.PlainDate(2001, 6, 3);
+
+['year', 'month', 'week', 'day'].forEach((largestUnit) => {
+ const result = instance.dateUntil(date, date, { largestUnit });
+ assert.sameValue(result.toString(), 'PT0S');
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..fdbf3263a7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.day
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.day({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.day({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/day/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..a539d386ad
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.dayofweek
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.dayOfWeek({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dayOfWeek({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfWeek/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..55e62fb72b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.dayofyear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.dayOfYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.dayOfYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/dayOfYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..91fdf547f3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.daysinmonth
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.daysInMonth({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.daysInMonth({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInMonth/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..d3e78fde8e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.daysinweek
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.daysInWeek({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.daysInWeek({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInWeek/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..7d5dae7441
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.daysinyear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.daysInYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.daysInYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/daysInYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-builtin-calendar-no-array-iteration.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-builtin-calendar-no-array-iteration.js
new file mode 100644
index 0000000000..91f3b5cf63
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-builtin-calendar-no-array-iteration.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Calling the method with a property bag argument with a builtin calendar causes
+ no observable array iteration when getting the calendar fields.
+features: [Temporal]
+---*/
+
+const arrayPrototypeSymbolIteratorOriginal = Array.prototype[Symbol.iterator];
+Array.prototype[Symbol.iterator] = function arrayIterator() {
+ throw new Test262Error("Array should not be iterated");
+}
+
+const instance = new Temporal.Calendar("iso8601");
+const arg = { year: 2000, month: 5, day: 2, calendar: "iso8601" };
+instance.era(arg);
+
+Array.prototype[Symbol.iterator] = arrayPrototypeSymbolIteratorOriginal;
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-calendar-datefromfields-called-with-null-prototype-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-calendar-datefromfields-called-with-null-prototype-fields.js
new file mode 100644
index 0000000000..43ebecc229
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-calendar-datefromfields-called-with-null-prototype-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Calendar.dateFromFields method is called with a null-prototype fields object
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarCheckFieldsPrototypePollution();
+const instance = new Temporal.Calendar("iso8601");
+const arg = { year: 2000, month: 5, day: 2, calendar };
+instance.era(arg);
+assert.sameValue(calendar.dateFromFieldsCallCount, 1, "dateFromFields should be called on the property bag's calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-constructor-in-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-constructor-in-calendar-fields.js
new file mode 100644
index 0000000000..66df73d80e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-constructor-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: If a calendar's fields() method returns a field named 'constructor', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['constructor']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(RangeError, () => instance.era(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-duplicate-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-duplicate-calendar-fields.js
new file mode 100644
index 0000000000..39f2c7a757
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-duplicate-calendar-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: If a calendar's fields() method returns duplicate field names, PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const extra_fields of [['foo', 'foo'], ['day'], ['month'], ['monthCode'], ['year']]) {
+ const calendar = TemporalHelpers.calendarWithExtraFields(extra_fields);
+ const arg = { year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar };
+ const instance = new Temporal.Calendar("iso8601");
+
+ assert.throws(RangeError, () => instance.era(arg));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-leap-second.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-leap-second.js
new file mode 100644
index 0000000000..eeae48c412
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-leap-second.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Leap second is a valid ISO string for PlainDate
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+let arg = "2016-12-31T23:59:60";
+const result1 = instance.era(arg);
+assert.sameValue(
+ result1,
+ undefined,
+ "leap second is a valid ISO string for PlainDate"
+);
+
+arg = { year: 2016, month: 12, day: 31, hour: 23, minute: 59, second: 60 };
+const result2 = instance.era(arg);
+assert.sameValue(
+ result2,
+ undefined,
+ "second: 60 is ignored in property bag for PlainDate"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-number.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-number.js
new file mode 100644
index 0000000000..a464d71968
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-number.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: A number cannot be used in place of a Temporal.PlainDate
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const numbers = [
+ 1,
+ 19761118,
+ -19761118,
+ 1234567890,
+];
+
+for (const arg of numbers) {
+ assert.throws(
+ TypeError,
+ () => instance.era(arg),
+ 'Numbers cannot be used in place of an ISO string for PlainDate'
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-case-insensitive.js
new file mode 100644
index 0000000000..5ef11cb688
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-case-insensitive.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: The calendar name is case-insensitive
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "IsO8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.era(arg);
+assert.sameValue(result, undefined, "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-leap-second.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-leap-second.js
new file mode 100644
index 0000000000..e4a7ea689f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-leap-second.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Leap second is a valid ISO string for a calendar in a property bag
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "2016-12-31T23:59:60";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.era(arg);
+assert.sameValue(
+ result,
+ undefined,
+ "leap second is a valid ISO string for calendar"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-number.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-number.js
new file mode 100644
index 0000000000..aaebffa0a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-number.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: A number as calendar in a property bag is not accepted
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const numbers = [
+ 1,
+ 19970327,
+ -19970327,
+ 1234567890,
+];
+
+for (const calendar of numbers) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ assert.throws(
+ TypeError,
+ () => instance.era(arg),
+ "Numbers cannot be used as a calendar"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-string.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-string.js
new file mode 100644
index 0000000000..12a89fcfe6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-string.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: A calendar ID is valid input for Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "iso8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.era(arg);
+assert.sameValue(result, undefined, `Calendar created from string "${calendar}"`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-wrong-type.js
new file mode 100644
index 0000000000..1c39744a16
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-wrong-type.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Appropriate error thrown when a calendar property from a property bag cannot
+ be converted to a calendar object or string
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.Calendar("iso8601");
+
+const primitiveTests = [
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [calendar, description] of primitiveTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(
+ typeof calendar === 'string' ? RangeError : TypeError,
+ () => instance.era(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object that doesn't implement the protocol"],
+ [new Temporal.TimeZone("UTC"), "time zone instance"],
+ [Temporal.Calendar, "Temporal.Calendar, object"],
+ [Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
+];
+
+for (const [calendar, description] of typeErrorTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(TypeError, () => instance.era(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-year-zero.js
new file mode 100644
index 0000000000..d0698c070c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-year-zero.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T17:45",
+ "-000000-10-31T17:45Z",
+ "-000000-10-31T17:45+01:00",
+ "-000000-10-31T17:45+00:00[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-proto-in-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-proto-in-calendar-fields.js
new file mode 100644
index 0000000000..69c004192b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-proto-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: If a calendar's fields() method returns a field named '__proto__', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['__proto__']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(RangeError, () => instance.era(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-calendar-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-calendar-annotation.js
new file mode 100644
index 0000000000..0d537e511d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-calendar-annotation.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Various forms of calendar annotation; critical flag has no effect
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[u-ca=iso8601]", "without time or time zone"],
+ ["2000-05-02[UTC][u-ca=iso8601]", "with time zone and no time"],
+ ["2000-05-02T15:23[u-ca=iso8601]", "without time zone"],
+ ["2000-05-02T15:23[UTC][u-ca=iso8601]", "with time zone"],
+ ["2000-05-02T15:23[!u-ca=iso8601]", "with ! and no time zone"],
+ ["2000-05-02T15:23[UTC][!u-ca=iso8601]", "with ! and time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][u-ca=discord]", "second annotation ignored"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.era(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `calendar annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-critical-unknown-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-critical-unknown-annotation.js
new file mode 100644
index 0000000000..5b4a441d16
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-critical-unknown-annotation.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Unknown annotations with critical flag are rejected
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[!foo=bar]",
+ "1970-01-01T00:00[!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar]",
+ "1970-01-01T00:00[u-ca=iso8601][!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar][u-ca=iso8601]",
+ "1970-01-01T00:00[foo=bar][!_foo-bar0=Dont-Ignore-This-99999999999]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ `reject unknown annotation with critical flag: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-date-with-utc-offset.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-date-with-utc-offset.js
new file mode 100644
index 0000000000..c425dea4db
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-date-with-utc-offset.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: UTC offset not valid with format that does not include a time
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const validStrings = [
+ "2000-05-02T00+00:00",
+ "2000-05-02T00+00:00[UTC]",
+ "2000-05-02T00+00:00[!UTC]",
+ "2000-05-02T00-02:30[America/St_Johns]",
+];
+
+for (const arg of validStrings) {
+ const result = instance.era(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `"${arg}" is a valid UTC offset with time for PlainDate`
+ );
+}
+
+const invalidStrings = [
+ "2022-09-15Z",
+ "2022-09-15Z[UTC]",
+ "2022-09-15Z[Europe/Vienna]",
+ "2022-09-15+00:00",
+ "2022-09-15+00:00[UTC]",
+ "2022-09-15-02:30",
+ "2022-09-15-02:30[America/St_Johns]",
+];
+
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ `"${arg}" UTC offset without time is not valid for PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-invalid.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-invalid.js
new file mode 100644
index 0000000000..b1e77d48a6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-invalid.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ RangeError thrown if an invalid ISO string (or syntactically valid ISO string
+ that is not supported) is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ // invalid ISO strings:
+ "",
+ "invalid iso8601",
+ "2020-01-00",
+ "2020-01-32",
+ "2020-02-30",
+ "2021-02-29",
+ "2020-00-01",
+ "2020-13-01",
+ "2020-01-01T",
+ "2020-01-01T25:00:00",
+ "2020-01-01T01:60:00",
+ "2020-01-01T01:60:61",
+ "2020-01-01junk",
+ "2020-01-01T00:00:00junk",
+ "2020-01-01T00:00:00+00:00junk",
+ "2020-01-01T00:00:00+00:00[UTC]junk",
+ "2020-01-01T00:00:00+00:00[UTC][u-ca=iso8601]junk",
+ "02020-01-01",
+ "2020-001-01",
+ "2020-01-001",
+ "2020-01-01T001",
+ "2020-01-01T01:001",
+ "2020-01-01T01:01:001",
+ // valid, but forms not supported in Temporal:
+ "2020-W01-1",
+ "2020-001",
+ "+0002020-01-01",
+ // valid, but this calendar must not exist:
+ "2020-01-01[u-ca=notexist]",
+ // may be valid in other contexts, but insufficient information for PlainDate:
+ "2020-01",
+ "+002020-01",
+ "01-01",
+ "2020-W01",
+ "P1Y",
+ "-P12Y",
+ // valid, but outside the supported range:
+ "-999999-01-01",
+ "+999999-01-01",
+];
+const instance = new Temporal.Calendar("iso8601");
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ `"${arg}" should not be a valid ISO string for a PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-calendar.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-calendar.js
new file mode 100644
index 0000000000..ab38062b05
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-calendar.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ More than one calendar annotation is not syntactical if any have the criical
+ flag
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ `reject more than one calendar annotation if any critical: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-time-zone.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-time-zone.js
new file mode 100644
index 0000000000..52d1445216
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-multiple-time-zone.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: More than one time zone annotation is not syntactical
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[UTC][UTC]",
+ "1970-01-01T00:00[UTC][UTC]",
+ "1970-01-01T00:00[!UTC][UTC]",
+ "1970-01-01T00:00[UTC][!UTC]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][UTC]",
+ "1970-01-01T00:00[UTC][foo=bar][UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ `reject more than one time zone annotation: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-separators.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-separators.js
new file mode 100644
index 0000000000..e3d6e7b62f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-separators.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Time separator in string argument can vary
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02T15:23", "uppercase T"],
+ ["2000-05-02t15:23", "lowercase T"],
+ ["2000-05-02 15:23", "space between date and time"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.era(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `variant time separators (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-zone-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-zone-annotation.js
new file mode 100644
index 0000000000..b6b41355b1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-time-zone-annotation.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Various forms of time zone annotation; critical flag has no effect
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[Asia/Kolkata]", "named, with no time"],
+ ["2000-05-02[!Europe/Vienna]", "named, with ! and no time"],
+ ["2000-05-02[+00:00]", "numeric, with no time"],
+ ["2000-05-02[!-02:30]", "numeric, with ! and no time"],
+ ["2000-05-02T15:23[America/Sao_Paulo]", "named, with no offset"],
+ ["2000-05-02T15:23[!Asia/Tokyo]", "named, with ! and no offset"],
+ ["2000-05-02T15:23[-02:30]", "numeric, with no offset"],
+ ["2000-05-02T15:23[!+00:00]", "numeric, with ! and no offset"],
+ ["2000-05-02T15:23+00:00[America/New_York]", "named, with offset"],
+ ["2000-05-02T15:23+00:00[!UTC]", "named, with offset and !"],
+ ["2000-05-02T15:23+00:00[+01:00]", "numeric, with offset"],
+ ["2000-05-02T15:23+00:00[!-08:00]", "numeric, with offset and !"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.era(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `time zone annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-unknown-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-unknown-annotation.js
new file mode 100644
index 0000000000..5949984435
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-unknown-annotation.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Various forms of unknown annotation
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[foo=bar]", "without time"],
+ ["2000-05-02T15:23[foo=bar]", "alone"],
+ ["2000-05-02T15:23[UTC][foo=bar]", "with time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][foo=bar]", "with calendar"],
+ ["2000-05-02T15:23[UTC][foo=bar][u-ca=iso8601]", "with time zone and calendar"],
+ ["2000-05-02T15:23[foo=bar][_foo-bar0=Ignore-This-999999999999]", "with another unknown annotation"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.era(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `unknown annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-with-utc-designator.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-with-utc-designator.js
new file mode 100644
index 0000000000..467398e21b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-string-with-utc-designator.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: RangeError thrown if a string with UTC designator is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "2019-10-01T09:00:00Z",
+ "2019-10-01T09:00:00Z[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ "String with UTC designator should not be valid as a PlainDate"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-wrong-type.js
new file mode 100644
index 0000000000..660bf41a47
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-wrong-type.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Appropriate error thrown when argument cannot be converted to a valid string
+ or property bag for PlainDate
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const primitiveTests = [
+ [undefined, "undefined"],
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [arg, description] of primitiveTests) {
+ assert.throws(
+ typeof arg === 'string' ? RangeError : TypeError,
+ () => instance.era(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object"],
+ [Temporal.PlainDate, "Temporal.PlainDate, object"],
+ [Temporal.PlainDate.prototype, "Temporal.PlainDate.prototype, object"],
+];
+
+for (const [arg, description] of typeErrorTests) {
+ assert.throws(TypeError, () => instance.era(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-convert.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-convert.js
new file mode 100644
index 0000000000..62a046358e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-convert.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: An exception from TimeZone#getOffsetNanosecondsFor() is propagated.
+features: [Temporal]
+---*/
+
+class TZ extends Temporal.TimeZone {
+ constructor() { super("UTC") }
+ getOffsetNanosecondsFor() { throw new Test262Error() }
+}
+
+const tz = new TZ();
+const arg = new Temporal.ZonedDateTime(0n, tz);
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(Test262Error, () => instance.era(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-slots.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-slots.js
new file mode 100644
index 0000000000..f7958d0d92
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-slots.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Getters are not called when converting a ZonedDateTime to a PlainDate.
+includes: [compareArray.js]
+features: [Temporal]
+---*/
+
+const actual = [];
+const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.ZonedDateTime.prototype);
+const getters = ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", "calendar"];
+
+for (const property of getters) {
+ Object.defineProperty(Temporal.ZonedDateTime.prototype, property, {
+ get() {
+ actual.push(`get ${property}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${property}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${property}`);
+ return value;
+ },
+ };
+ },
+ });
+}
+
+const arg = new Temporal.ZonedDateTime(0n, "UTC");
+const instance = new Temporal.Calendar("iso8601");
+instance.era(arg);
+assert.compareArray(actual, []);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..1c31fe0571
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => calendar.era(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..8b97cf8f65
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => calendar.era(datetime),
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..1ee0615f18
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => calendar.era(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..be7985d3ef
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => calendar.era(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/branding.js
new file mode 100644
index 0000000000..f2ae214643
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Temporal.Calendar.prototype.era;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.Calendar), "Temporal.Calendar");
+assert.throws(TypeError, () => era.call(Temporal.Calendar.prototype), "Temporal.Calendar.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/builtin.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/builtin.js
new file mode 100644
index 0000000000..4a5da3447f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/builtin.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Tests that Temporal.Calendar.prototype.era
+ meets the requirements for built-in objects defined by the
+ introduction of chapter 17 of the ECMAScript Language Specification.
+info: |
+ Built-in functions that are not constructors do not have a "prototype" property unless
+ otherwise specified in the description of a particular function.
+
+ Unless specified otherwise, a built-in object that is callable as a function is a built-in
+ function object with the characteristics described in 10.3. Unless specified otherwise, the
+ [[Extensible]] internal slot of a built-in object initially has the value true.
+
+ Unless otherwise specified every built-in function and every built-in constructor has the
+ Function prototype object [...] as the value of its [[Prototype]] internal slot.
+features: [Temporal]
+---*/
+
+assert.sameValue(Object.isExtensible(Temporal.Calendar.prototype.era),
+ true, "Built-in objects must be extensible.");
+
+assert.sameValue(Object.prototype.toString.call(Temporal.Calendar.prototype.era),
+ "[object Function]", "Object.prototype.toString");
+
+assert.sameValue(Object.getPrototypeOf(Temporal.Calendar.prototype.era),
+ Function.prototype, "prototype");
+
+assert.sameValue(Temporal.Calendar.prototype.era.hasOwnProperty("prototype"),
+ false, "prototype property");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/calendar-datefromfields-called-with-options-undefined.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/calendar-datefromfields-called-with-options-undefined.js
new file mode 100644
index 0000000000..9337ec6781
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/calendar-datefromfields-called-with-options-undefined.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Calendar.dateFromFields method is called with undefined as the options value
+ when call originates internally
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarFromFieldsUndefinedOptions();
+calendar.era({ year: 2000, month: 5, day: 3, calendar });
+assert.sameValue(calendar.dateFromFieldsCallCount, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..361c40927e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.era
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.era({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.era({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/length.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/length.js
new file mode 100644
index 0000000000..60121001e1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/length.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Temporal.Calendar.prototype.era.length is 1
+info: |
+ Every built-in function object, including constructors, has a "length" property whose value is
+ an integer. Unless otherwise specified, this value is equal to the largest number of named
+ arguments shown in the subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form
+ «...name») are not included in the default argument count.
+
+ Unless otherwise specified, the "length" property of a built-in function object has the
+ attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.Calendar.prototype.era, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/name.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/name.js
new file mode 100644
index 0000000000..3f4180f251
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/name.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Temporal.Calendar.prototype.era.name is "era".
+info: |
+ Every built-in function object, including constructors, that is not identified as an anonymous
+ function has a "name" property whose value is a String. Unless otherwise specified, this value
+ is the name that is given to the function in this specification.
+
+ Unless otherwise specified, the "name" property of a built-in function object, if it exists,
+ has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.Calendar.prototype.era, "name", {
+ value: "era",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/not-a-constructor.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/not-a-constructor.js
new file mode 100644
index 0000000000..04a80c93be
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/not-a-constructor.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: >
+ Temporal.Calendar.prototype.era does not implement [[Construct]], is not new-able
+info: |
+ Built-in function objects that are not identified as constructors do not implement the
+ [[Construct]] internal method unless otherwise specified in the description of a particular
+ function.
+includes: [isConstructor.js]
+features: [Reflect.construct, Temporal]
+---*/
+
+assert.throws(TypeError, () => {
+ new Temporal.Calendar.prototype.era();
+}, "Calling as constructor");
+
+assert.sameValue(isConstructor(Temporal.Calendar.prototype.era), false,
+ "isConstructor(Temporal.Calendar.prototype.era)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..e60bda1539
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: The "era" property of Temporal.Calendar.prototype
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+assert.sameValue(
+ typeof Temporal.Calendar.prototype.era,
+ "function",
+ "`typeof Calendar.prototype.era` is `function`"
+);
+
+verifyProperty(Temporal.Calendar.prototype, "era", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/shell.js
new file mode 100644
index 0000000000..32df7c4217
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/shell.js
@@ -0,0 +1,2182 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
+
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/year-zero.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/year-zero.js
new file mode 100644
index 0000000000..1f9c6a9ca5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/era/year-zero.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.era
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T00:45",
+ "-000000-10-31T00:45+01:00",
+ "-000000-10-31T00:45+00:00[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.era(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-builtin-calendar-no-array-iteration.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-builtin-calendar-no-array-iteration.js
new file mode 100644
index 0000000000..a2b5a5afcf
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-builtin-calendar-no-array-iteration.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Calling the method with a property bag argument with a builtin calendar causes
+ no observable array iteration when getting the calendar fields.
+features: [Temporal]
+---*/
+
+const arrayPrototypeSymbolIteratorOriginal = Array.prototype[Symbol.iterator];
+Array.prototype[Symbol.iterator] = function arrayIterator() {
+ throw new Test262Error("Array should not be iterated");
+}
+
+const instance = new Temporal.Calendar("iso8601");
+const arg = { year: 2000, month: 5, day: 2, calendar: "iso8601" };
+instance.eraYear(arg);
+
+Array.prototype[Symbol.iterator] = arrayPrototypeSymbolIteratorOriginal;
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-calendar-datefromfields-called-with-null-prototype-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-calendar-datefromfields-called-with-null-prototype-fields.js
new file mode 100644
index 0000000000..5de9f82a13
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-calendar-datefromfields-called-with-null-prototype-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Calendar.dateFromFields method is called with a null-prototype fields object
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarCheckFieldsPrototypePollution();
+const instance = new Temporal.Calendar("iso8601");
+const arg = { year: 2000, month: 5, day: 2, calendar };
+instance.eraYear(arg);
+assert.sameValue(calendar.dateFromFieldsCallCount, 1, "dateFromFields should be called on the property bag's calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-constructor-in-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-constructor-in-calendar-fields.js
new file mode 100644
index 0000000000..525d21d4fc
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-constructor-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: If a calendar's fields() method returns a field named 'constructor', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['constructor']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(RangeError, () => instance.eraYear(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-duplicate-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-duplicate-calendar-fields.js
new file mode 100644
index 0000000000..243fb8de8a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-duplicate-calendar-fields.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: If a calendar's fields() method returns duplicate field names, PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+for (const extra_fields of [['foo', 'foo'], ['day'], ['month'], ['monthCode'], ['year']]) {
+ const calendar = TemporalHelpers.calendarWithExtraFields(extra_fields);
+ const arg = { year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar };
+ const instance = new Temporal.Calendar("iso8601");
+
+ assert.throws(RangeError, () => instance.eraYear(arg));
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-leap-second.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-leap-second.js
new file mode 100644
index 0000000000..e0fa4fa9a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-leap-second.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Leap second is a valid ISO string for PlainDate
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+let arg = "2016-12-31T23:59:60";
+const result1 = instance.eraYear(arg);
+assert.sameValue(
+ result1,
+ undefined,
+ "leap second is a valid ISO string for PlainDate"
+);
+
+arg = { year: 2016, month: 12, day: 31, hour: 23, minute: 59, second: 60 };
+const result2 = instance.eraYear(arg);
+assert.sameValue(
+ result2,
+ undefined,
+ "second: 60 is ignored in property bag for PlainDate"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-number.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-number.js
new file mode 100644
index 0000000000..9417849c6a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-number.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: A number cannot be used in place of a Temporal.PlainDate
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const numbers = [
+ 1,
+ 19761118,
+ -19761118,
+ 1234567890,
+];
+
+for (const arg of numbers) {
+ assert.throws(
+ TypeError,
+ () => instance.eraYear(arg),
+ 'Numbers cannot be used in place of an ISO string for PlainDate'
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-case-insensitive.js
new file mode 100644
index 0000000000..40e048aa59
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-case-insensitive.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: The calendar name is case-insensitive
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "IsO8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.eraYear(arg);
+assert.sameValue(result, undefined, "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-leap-second.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-leap-second.js
new file mode 100644
index 0000000000..845624b518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-leap-second.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Leap second is a valid ISO string for a calendar in a property bag
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "2016-12-31T23:59:60";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.eraYear(arg);
+assert.sameValue(
+ result,
+ undefined,
+ "leap second is a valid ISO string for calendar"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-number.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-number.js
new file mode 100644
index 0000000000..e9d8fc746c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-number.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: A number as calendar in a property bag is not accepted
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const numbers = [
+ 1,
+ 19970327,
+ -19970327,
+ 1234567890,
+];
+
+for (const calendar of numbers) {
+ const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+ assert.throws(
+ TypeError,
+ () => instance.eraYear(arg),
+ "Numbers cannot be used as a calendar"
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-string.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-string.js
new file mode 100644
index 0000000000..d40d3152a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-string.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: A calendar ID is valid input for Calendar
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const calendar = "iso8601";
+
+const arg = { year: 1976, monthCode: "M11", day: 18, calendar };
+const result = instance.eraYear(arg);
+assert.sameValue(result, undefined, `Calendar created from string "${calendar}"`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-wrong-type.js
new file mode 100644
index 0000000000..5b75cd27e5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-wrong-type.js
@@ -0,0 +1,46 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Appropriate error thrown when a calendar property from a property bag cannot
+ be converted to a calendar object or string
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("UTC");
+const instance = new Temporal.Calendar("iso8601");
+
+const primitiveTests = [
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [calendar, description] of primitiveTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(
+ typeof calendar === 'string' ? RangeError : TypeError,
+ () => instance.eraYear(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object that doesn't implement the protocol"],
+ [new Temporal.TimeZone("UTC"), "time zone instance"],
+ [Temporal.Calendar, "Temporal.Calendar, object"],
+ [Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
+];
+
+for (const [calendar, description] of typeErrorTests) {
+ const arg = { year: 2019, monthCode: "M11", day: 1, calendar };
+ assert.throws(TypeError, () => instance.eraYear(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-year-zero.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-year-zero.js
new file mode 100644
index 0000000000..65675a5894
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-year-zero.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T17:45",
+ "-000000-10-31T17:45Z",
+ "-000000-10-31T17:45+01:00",
+ "-000000-10-31T17:45+00:00[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-proto-in-calendar-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-proto-in-calendar-fields.js
new file mode 100644
index 0000000000..a664730c59
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-proto-in-calendar-fields.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: If a calendar's fields() method returns a field named '__proto__', PrepareTemporalFields should throw a RangeError.
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarWithExtraFields(['__proto__']);
+const arg = {year: 2023, month: 5, monthCode: 'M05', day: 1, calendar: calendar};
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(RangeError, () => instance.eraYear(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-calendar-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-calendar-annotation.js
new file mode 100644
index 0000000000..562c2fdee1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-calendar-annotation.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Various forms of calendar annotation; critical flag has no effect
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[u-ca=iso8601]", "without time or time zone"],
+ ["2000-05-02[UTC][u-ca=iso8601]", "with time zone and no time"],
+ ["2000-05-02T15:23[u-ca=iso8601]", "without time zone"],
+ ["2000-05-02T15:23[UTC][u-ca=iso8601]", "with time zone"],
+ ["2000-05-02T15:23[!u-ca=iso8601]", "with ! and no time zone"],
+ ["2000-05-02T15:23[UTC][!u-ca=iso8601]", "with ! and time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][u-ca=discord]", "second annotation ignored"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.eraYear(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `calendar annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-critical-unknown-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-critical-unknown-annotation.js
new file mode 100644
index 0000000000..63419054b5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-critical-unknown-annotation.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Unknown annotations with critical flag are rejected
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[!foo=bar]",
+ "1970-01-01T00:00[!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar]",
+ "1970-01-01T00:00[u-ca=iso8601][!foo=bar]",
+ "1970-01-01T00:00[UTC][!foo=bar][u-ca=iso8601]",
+ "1970-01-01T00:00[foo=bar][!_foo-bar0=Dont-Ignore-This-99999999999]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ `reject unknown annotation with critical flag: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-date-with-utc-offset.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-date-with-utc-offset.js
new file mode 100644
index 0000000000..0ad32ed291
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-date-with-utc-offset.js
@@ -0,0 +1,48 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: UTC offset not valid with format that does not include a time
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const validStrings = [
+ "2000-05-02T00+00:00",
+ "2000-05-02T00+00:00[UTC]",
+ "2000-05-02T00+00:00[!UTC]",
+ "2000-05-02T00-02:30[America/St_Johns]",
+];
+
+for (const arg of validStrings) {
+ const result = instance.eraYear(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `"${arg}" is a valid UTC offset with time for PlainDate`
+ );
+}
+
+const invalidStrings = [
+ "2022-09-15Z",
+ "2022-09-15Z[UTC]",
+ "2022-09-15Z[Europe/Vienna]",
+ "2022-09-15+00:00",
+ "2022-09-15+00:00[UTC]",
+ "2022-09-15-02:30",
+ "2022-09-15-02:30[America/St_Johns]",
+];
+
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ `"${arg}" UTC offset without time is not valid for PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-invalid.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-invalid.js
new file mode 100644
index 0000000000..721a903b93
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-invalid.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ RangeError thrown if an invalid ISO string (or syntactically valid ISO string
+ that is not supported) is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ // invalid ISO strings:
+ "",
+ "invalid iso8601",
+ "2020-01-00",
+ "2020-01-32",
+ "2020-02-30",
+ "2021-02-29",
+ "2020-00-01",
+ "2020-13-01",
+ "2020-01-01T",
+ "2020-01-01T25:00:00",
+ "2020-01-01T01:60:00",
+ "2020-01-01T01:60:61",
+ "2020-01-01junk",
+ "2020-01-01T00:00:00junk",
+ "2020-01-01T00:00:00+00:00junk",
+ "2020-01-01T00:00:00+00:00[UTC]junk",
+ "2020-01-01T00:00:00+00:00[UTC][u-ca=iso8601]junk",
+ "02020-01-01",
+ "2020-001-01",
+ "2020-01-001",
+ "2020-01-01T001",
+ "2020-01-01T01:001",
+ "2020-01-01T01:01:001",
+ // valid, but forms not supported in Temporal:
+ "2020-W01-1",
+ "2020-001",
+ "+0002020-01-01",
+ // valid, but this calendar must not exist:
+ "2020-01-01[u-ca=notexist]",
+ // may be valid in other contexts, but insufficient information for PlainDate:
+ "2020-01",
+ "+002020-01",
+ "01-01",
+ "2020-W01",
+ "P1Y",
+ "-P12Y",
+ // valid, but outside the supported range:
+ "-999999-01-01",
+ "+999999-01-01",
+];
+const instance = new Temporal.Calendar("iso8601");
+for (const arg of invalidStrings) {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ `"${arg}" should not be a valid ISO string for a PlainDate`
+ );
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-calendar.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-calendar.js
new file mode 100644
index 0000000000..c72b51b846
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-calendar.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ More than one calendar annotation is not syntactical if any have the criical
+ flag
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[!u-ca=iso8601][u-ca=iso8601]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][!u-ca=iso8601]",
+ "1970-01-01T00:00[u-ca=iso8601][foo=bar][!u-ca=iso8601]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ `reject more than one calendar annotation if any critical: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-time-zone.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-time-zone.js
new file mode 100644
index 0000000000..7f73fd08c7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-multiple-time-zone.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: More than one time zone annotation is not syntactical
+features: [Temporal]
+---*/
+
+const invalidStrings = [
+ "1970-01-01[UTC][UTC]",
+ "1970-01-01T00:00[UTC][UTC]",
+ "1970-01-01T00:00[!UTC][UTC]",
+ "1970-01-01T00:00[UTC][!UTC]",
+ "1970-01-01T00:00[UTC][u-ca=iso8601][UTC]",
+ "1970-01-01T00:00[UTC][foo=bar][UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ `reject more than one time zone annotation: ${arg}`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-separators.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-separators.js
new file mode 100644
index 0000000000..df883826af
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-separators.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Time separator in string argument can vary
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02T15:23", "uppercase T"],
+ ["2000-05-02t15:23", "lowercase T"],
+ ["2000-05-02 15:23", "space between date and time"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.eraYear(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `variant time separators (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-zone-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-zone-annotation.js
new file mode 100644
index 0000000000..6a2cb0b7e5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-zone-annotation.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Various forms of time zone annotation; critical flag has no effect
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[Asia/Kolkata]", "named, with no time"],
+ ["2000-05-02[!Europe/Vienna]", "named, with ! and no time"],
+ ["2000-05-02[+00:00]", "numeric, with no time"],
+ ["2000-05-02[!-02:30]", "numeric, with ! and no time"],
+ ["2000-05-02T15:23[America/Sao_Paulo]", "named, with no offset"],
+ ["2000-05-02T15:23[!Asia/Tokyo]", "named, with ! and no offset"],
+ ["2000-05-02T15:23[-02:30]", "numeric, with no offset"],
+ ["2000-05-02T15:23[!+00:00]", "numeric, with ! and no offset"],
+ ["2000-05-02T15:23+00:00[America/New_York]", "named, with offset"],
+ ["2000-05-02T15:23+00:00[!UTC]", "named, with offset and !"],
+ ["2000-05-02T15:23+00:00[+01:00]", "numeric, with offset"],
+ ["2000-05-02T15:23+00:00[!-08:00]", "numeric, with offset and !"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.eraYear(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `time zone annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-unknown-annotation.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-unknown-annotation.js
new file mode 100644
index 0000000000..4540e6c7ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-unknown-annotation.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Various forms of unknown annotation
+features: [Temporal]
+---*/
+
+const tests = [
+ ["2000-05-02[foo=bar]", "without time"],
+ ["2000-05-02T15:23[foo=bar]", "alone"],
+ ["2000-05-02T15:23[UTC][foo=bar]", "with time zone"],
+ ["2000-05-02T15:23[u-ca=iso8601][foo=bar]", "with calendar"],
+ ["2000-05-02T15:23[UTC][foo=bar][u-ca=iso8601]", "with time zone and calendar"],
+ ["2000-05-02T15:23[foo=bar][_foo-bar0=Ignore-This-999999999999]", "with another unknown annotation"],
+];
+
+const instance = new Temporal.Calendar("iso8601");
+
+tests.forEach(([arg, description]) => {
+ const result = instance.eraYear(arg);
+
+ assert.sameValue(
+ result,
+ undefined,
+ `unknown annotation (${description})`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-with-utc-designator.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-with-utc-designator.js
new file mode 100644
index 0000000000..378aa0e5d4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-string-with-utc-designator.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: RangeError thrown if a string with UTC designator is used as a PlainDate
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "2019-10-01T09:00:00Z",
+ "2019-10-01T09:00:00Z[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ "String with UTC designator should not be valid as a PlainDate"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-wrong-type.js
new file mode 100644
index 0000000000..41af3fe618
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-wrong-type.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Appropriate error thrown when argument cannot be converted to a valid string
+ or property bag for PlainDate
+features: [BigInt, Symbol, Temporal]
+---*/
+
+const instance = new Temporal.Calendar("iso8601");
+
+const primitiveTests = [
+ [undefined, "undefined"],
+ [null, "null"],
+ [true, "boolean"],
+ ["", "empty string"],
+ [1, "number that doesn't convert to a valid ISO string"],
+ [1n, "bigint"],
+];
+
+for (const [arg, description] of primitiveTests) {
+ assert.throws(
+ typeof arg === 'string' ? RangeError : TypeError,
+ () => instance.eraYear(arg),
+ `${description} does not convert to a valid ISO string`
+ );
+}
+
+const typeErrorTests = [
+ [Symbol(), "symbol"],
+ [{}, "plain object"],
+ [Temporal.PlainDate, "Temporal.PlainDate, object"],
+ [Temporal.PlainDate.prototype, "Temporal.PlainDate.prototype, object"],
+];
+
+for (const [arg, description] of typeErrorTests) {
+ assert.throws(TypeError, () => instance.eraYear(arg), `${description} is not a valid property bag and does not convert to a string`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-convert.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-convert.js
new file mode 100644
index 0000000000..07abdb0eb7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-convert.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: An exception from TimeZone#getOffsetNanosecondsFor() is propagated.
+features: [Temporal]
+---*/
+
+class TZ extends Temporal.TimeZone {
+ constructor() { super("UTC") }
+ getOffsetNanosecondsFor() { throw new Test262Error() }
+}
+
+const tz = new TZ();
+const arg = new Temporal.ZonedDateTime(0n, tz);
+const instance = new Temporal.Calendar("iso8601");
+
+assert.throws(Test262Error, () => instance.eraYear(arg));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-slots.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-slots.js
new file mode 100644
index 0000000000..94bef9152a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-slots.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Getters are not called when converting a ZonedDateTime to a PlainDate.
+includes: [compareArray.js]
+features: [Temporal]
+---*/
+
+const actual = [];
+const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.ZonedDateTime.prototype);
+const getters = ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", "calendar"];
+
+for (const property of getters) {
+ Object.defineProperty(Temporal.ZonedDateTime.prototype, property, {
+ get() {
+ actual.push(`get ${property}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${property}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${property}`);
+ return value;
+ },
+ };
+ },
+ });
+}
+
+const arg = new Temporal.ZonedDateTime(0n, "UTC");
+const instance = new Temporal.Calendar("iso8601");
+instance.eraYear(arg);
+assert.compareArray(actual, []);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..ee263584bd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => calendar.eraYear(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..09ce6a6f72
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => calendar.eraYear(datetime),
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..1ba9d767f4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => calendar.eraYear(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..7341b73e02
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => calendar.eraYear(datetime));
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..c0ef6c041f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Temporal.Calendar.prototype.eraYear;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.Calendar), "Temporal.Calendar");
+assert.throws(TypeError, () => eraYear.call(Temporal.Calendar.prototype), "Temporal.Calendar.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/builtin.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/builtin.js
new file mode 100644
index 0000000000..eb67b158e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/builtin.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Tests that Temporal.Calendar.prototype.eraYear
+ meets the requirements for built-in objects defined by the
+ introduction of chapter 17 of the ECMAScript Language Specification.
+info: |
+ Built-in functions that are not constructors do not have a "prototype" property unless
+ otherwise specified in the description of a particular function.
+
+ Unless specified otherwise, a built-in object that is callable as a function is a built-in
+ function object with the characteristics described in 10.3. Unless specified otherwise, the
+ [[Extensible]] internal slot of a built-in object initially has the value true.
+
+ Unless otherwise specified every built-in function and every built-in constructor has the
+ Function prototype object [...] as the value of its [[Prototype]] internal slot.
+features: [Temporal]
+---*/
+
+assert.sameValue(Object.isExtensible(Temporal.Calendar.prototype.eraYear),
+ true, "Built-in objects must be extensible.");
+
+assert.sameValue(Object.prototype.toString.call(Temporal.Calendar.prototype.eraYear),
+ "[object Function]", "Object.prototype.toString");
+
+assert.sameValue(Object.getPrototypeOf(Temporal.Calendar.prototype.eraYear),
+ Function.prototype, "prototype");
+
+assert.sameValue(Temporal.Calendar.prototype.eraYear.hasOwnProperty("prototype"),
+ false, "prototype property");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/calendar-datefromfields-called-with-options-undefined.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/calendar-datefromfields-called-with-options-undefined.js
new file mode 100644
index 0000000000..93e2bf8077
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/calendar-datefromfields-called-with-options-undefined.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Calendar.dateFromFields method is called with undefined as the options value
+ when call originates internally
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const calendar = TemporalHelpers.calendarFromFieldsUndefinedOptions();
+calendar.eraYear({ year: 2000, month: 5, day: 3, calendar });
+assert.sameValue(calendar.dateFromFieldsCallCount, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..1fd4ce4133
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.erayear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.eraYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.eraYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/length.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/length.js
new file mode 100644
index 0000000000..28d75bc46a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/length.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Temporal.Calendar.prototype.eraYear.length is 1
+info: |
+ Every built-in function object, including constructors, has a "length" property whose value is
+ an integer. Unless otherwise specified, this value is equal to the largest number of named
+ arguments shown in the subclause headings for the function description. Optional parameters
+ (which are indicated with brackets: [ ]) or rest parameters (which are shown using the form
+ «...name») are not included in the default argument count.
+
+ Unless otherwise specified, the "length" property of a built-in function object has the
+ attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.Calendar.prototype.eraYear, "length", {
+ value: 1,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/name.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/name.js
new file mode 100644
index 0000000000..017064e6e8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/name.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Temporal.Calendar.prototype.eraYear.name is "eraYear".
+info: |
+ Every built-in function object, including constructors, that is not identified as an anonymous
+ function has a "name" property whose value is a String. Unless otherwise specified, this value
+ is the name that is given to the function in this specification.
+
+ Unless otherwise specified, the "name" property of a built-in function object, if it exists,
+ has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+verifyProperty(Temporal.Calendar.prototype.eraYear, "name", {
+ value: "eraYear",
+ writable: false,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js
new file mode 100644
index 0000000000..eacc07e32e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: >
+ Temporal.Calendar.prototype.eraYear does not implement [[Construct]], is not new-able
+info: |
+ Built-in function objects that are not identified as constructors do not implement the
+ [[Construct]] internal method unless otherwise specified in the description of a particular
+ function.
+includes: [isConstructor.js]
+features: [Reflect.construct, Temporal]
+---*/
+
+assert.throws(TypeError, () => {
+ new Temporal.Calendar.prototype.eraYear();
+}, "Calling as constructor");
+
+assert.sameValue(isConstructor(Temporal.Calendar.prototype.eraYear), false,
+ "isConstructor(Temporal.Calendar.prototype.eraYear)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..2e852441d3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/prop-desc.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: The "eraYear" property of Temporal.Calendar.prototype
+includes: [propertyHelper.js]
+features: [Temporal]
+---*/
+
+assert.sameValue(
+ typeof Temporal.Calendar.prototype.eraYear,
+ "function",
+ "`typeof Calendar.prototype.eraYear` is `function`"
+);
+
+verifyProperty(Temporal.Calendar.prototype, "eraYear", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..32df7c4217
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/shell.js
@@ -0,0 +1,2182 @@
+// GENERATED, DO NOT EDIT
+// file: isConstructor.js
+// Copyright (C) 2017 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: |
+ Test if a given function is a constructor function.
+defines: [isConstructor]
+features: [Reflect.construct]
+---*/
+
+function isConstructor(f) {
+ if (typeof f !== "function") {
+ throw new Test262Error("isConstructor invoked with a non-function value");
+ }
+
+ try {
+ Reflect.construct(function(){}, [], f);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
+
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/year-zero.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/year-zero.js
new file mode 100644
index 0000000000..22609178b1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/eraYear/year-zero.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.erayear
+description: Negative zero, as an extended year, is rejected
+features: [Temporal, arrow-function]
+---*/
+
+const invalidStrings = [
+ "-000000-10-31",
+ "-000000-10-31T00:45",
+ "-000000-10-31T00:45+01:00",
+ "-000000-10-31T00:45+00:00[UTC]",
+];
+const instance = new Temporal.Calendar("iso8601");
+invalidStrings.forEach((arg) => {
+ assert.throws(
+ RangeError,
+ () => instance.eraYear(arg),
+ "reject minus zero as extended year"
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..a6b94c84ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.inleapyear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.inLeapYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.inLeapYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/inLeapYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/gregorian-mutually-exclusive-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/gregorian-mutually-exclusive-fields.js
new file mode 100644
index 0000000000..f1144a2503
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/gregorian-mutually-exclusive-fields.js
@@ -0,0 +1,108 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.mergefields
+description: Calendar-specific mutually exclusive keys in mergeFields
+features: [Temporal]
+---*/
+
+function assertEntriesEqual(actual, expectedEntries, message) {
+ const names = Object.getOwnPropertyNames(actual);
+ const symbols = Object.getOwnPropertySymbols(actual);
+ const actualKeys = names.concat(symbols);
+ assert.sameValue(
+ actualKeys.length,
+ expectedEntries.length,
+ `${message}: expected object to have ${expectedEntries.length} properties, not ${actualKeys.length}:`
+ );
+ for (var index = 0; index < actualKeys.length; index++) {
+ const actualKey = actualKeys[index];
+ const expectedKey = expectedEntries[index][0];
+ const expectedValue = expectedEntries[index][1];
+ assert.sameValue(actualKey, expectedKey, `${message}: key ${index}:`);
+ assert.sameValue(actual[actualKey], expectedValue, `${message}: value ${index}:`);
+ }
+}
+
+const instance = new Temporal.Calendar("gregory");
+
+const fullFields = {
+ era: "ce",
+ eraYear: 1981,
+ year: 1981,
+ month: 12,
+ monthCode: "M12",
+ day: 15,
+};
+
+assertEntriesEqual(instance.mergeFields(fullFields, { era: "bce", eraYear: 1 }), [
+ ["era", "bce"],
+ ["eraYear", 1],
+ ["month", 12],
+ ["monthCode", "M12"],
+ ["day", 15],
+], "era and eraYear together exclude year");
+
+assertEntriesEqual(instance.mergeFields(fullFields, { year: -2 }), [
+ ["year", -2],
+ ["month", 12],
+ ["monthCode", "M12"],
+ ["day", 15],
+], "year excludes era and eraYear");
+
+assertEntriesEqual(instance.mergeFields(fullFields, { month: 5 }), [
+ ["era", "ce"],
+ ["eraYear", 1981],
+ ["year", 1981],
+ ["month", 5],
+ ["day", 15],
+], "month excludes monthCode");
+
+assertEntriesEqual(instance.mergeFields(fullFields, { monthCode: "M05" }), [
+ ["era", "ce"],
+ ["eraYear", 1981],
+ ["year", 1981],
+ ["monthCode", "M05"],
+ ["day", 15],
+], "monthCode excludes month");
+
+// Specific test cases, of mergeFields on information that is not complete
+// enough to construct a PlainDate from, as discussed in
+// https://github.com/tc39/proposal-temporal/issues/2407:
+
+assertEntriesEqual(instance.mergeFields({ day: 25, monthCode: "M12", year: 1997, era: "bce" }, { eraYear: 1 }), [
+ ["day", 25],
+ ["monthCode", "M12"],
+ ["eraYear", 1],
+], "eraYear excludes year and era");
+
+assertEntriesEqual(instance.mergeFields({ day: 25, monthCode: "M12", era: "bce" }, { eraYear: 1, year: 1997 }), [
+ ["day", 25],
+ ["monthCode", "M12"],
+ ["eraYear", 1],
+ ["year", 1997],
+], "eraYear and year both exclude era");
+
+assertEntriesEqual(instance.mergeFields({ day: 25, monthCode: "M12", eraYear: 1 }, { era: "bce", year: 1997 }), [
+ ["day", 25],
+ ["monthCode", "M12"],
+ ["era", "bce"],
+ ["year", 1997],
+], "era and year both exclude eraYear");
+
+assertEntriesEqual(instance.mergeFields({ day: 25, monthCode: "M12", year: 1997, eraYear: 1 }, { era: "bce" }), [
+ ["day", 25],
+ ["monthCode", "M12"],
+ ["era", "bce"],
+], "era excludes year and eraYear");
+
+assertEntriesEqual(instance.mergeFields({ day: 25, monthCode: "M12", year: 1997 }, { eraYear: 1, year: 2 }), [
+ ["day", 25],
+ ["monthCode", "M12"],
+ ["year", 2],
+ ["eraYear", 1],
+], "eraYear excludes year and era, year overwritten");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/japanese-mutually-exclusive-fields.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/japanese-mutually-exclusive-fields.js
new file mode 100644
index 0000000000..6742dc134a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/japanese-mutually-exclusive-fields.js
@@ -0,0 +1,80 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.mergefields
+description: Calendar-specific mutually exclusive keys in mergeFields
+features: [Temporal]
+---*/
+
+function assertEntriesEqual(actual, expectedEntries, message) {
+ const names = Object.getOwnPropertyNames(actual);
+ const symbols = Object.getOwnPropertySymbols(actual);
+ const actualKeys = names.concat(symbols);
+ assert.sameValue(
+ actualKeys.length,
+ expectedEntries.length,
+ `${message}: expected object to have ${expectedEntries.length} properties, not ${actualKeys.length}:`
+ );
+ for (var index = 0; index < actualKeys.length; index++) {
+ const actualKey = actualKeys[index];
+ const expectedKey = expectedEntries[index][0];
+ const expectedValue = expectedEntries[index][1];
+ assert.sameValue(actualKey, expectedKey, `${message}: key ${index}:`);
+ assert.sameValue(actual[actualKey], expectedValue, `${message}: value ${index}:`);
+ }
+}
+
+const instance = new Temporal.Calendar("japanese");
+
+const lastDayOfShowaFields = { era: "showa", eraYear: 64, year: 1989, month: 1, monthCode: "M01", day: 7 };
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { day: 10 }), [
+ ["year", 1989],
+ ["month", 1],
+ ["monthCode", "M01"],
+ ["day", 10],
+], "day excludes era and eraYear");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { month: 2 }), [
+ ["year", 1989],
+ ["month", 2],
+ ["day", 7],
+], "month excludes monthCode, era, and eraYear");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { monthCode: "M03" }), [
+ ["year", 1989],
+ ["monthCode", "M03"],
+ ["day", 7],
+], "monthCode excludes month, era, and eraYear");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { year: 1988 }), [
+ ["year", 1988],
+ ["month", 1],
+ ["monthCode", "M01"],
+ ["day", 7],
+], "year excludes era and eraYear (within same era)");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { year: 1990 }), [
+ ["year", 1990],
+ ["month", 1],
+ ["monthCode", "M01"],
+ ["day", 7],
+], "year excludes era and eraYear (in a different era)");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { eraYear: 1 }), [
+ ["eraYear", 1],
+ ["month", 1],
+ ["monthCode", "M01"],
+ ["day", 7],
+], "eraYear excludes year and era");
+
+assertEntriesEqual(instance.mergeFields(lastDayOfShowaFields, { era: "heisei" }), [
+ ["era", "heisei"],
+ ["month", 1],
+ ["monthCode", "M01"],
+ ["day", 7],
+], "era excludes year and eraYear");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/mergeFields/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..81cf5f9062
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.month
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.month({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.month({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/month/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..8705857e6b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.monthcode
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.monthCode({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.monthCode({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthCode/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/fields-underspecified.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/fields-underspecified.js
new file mode 100644
index 0000000000..7baa6db695
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/fields-underspecified.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthdayfromfields
+description: Throw a RangeError if only one of era/eraYear fields is present
+features: [Temporal]
+---*/
+
+const tests = [
+ ["gregory", { year: 2000, month: 5, day: 2, era: "ce" }, "era present but not eraYear"],
+ ["gregory", { year: 2000, month: 5, day: 2, eraYear: 1 }, "eraYear present but not era"],
+ ["gregory", { month: 8, day: 1 }, "no monthCode or year specification, non-ISO Gregorian"],
+ ["hebrew", { month: 8, day: 1 }, "no monthCode or year specification, non-ISO non-Gregorian"],
+];
+
+for (const [calendarId, arg, description] of tests) {
+ const instance = new Temporal.Calendar(calendarId);
+ assert.throws(TypeError, () => instance.monthDayFromFields(arg), description);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..552873bb6f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/infinity-throws-rangeerror.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.monthdayfromfields
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => instance.monthDayFromFields({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.monthDayFromFields({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/order-of-operations.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/order-of-operations.js
new file mode 100644
index 0000000000..6f0b350b22
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/order-of-operations.js
@@ -0,0 +1,79 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthdayfromfields
+description: Properties on objects passed to monthDayFromFields() are accessed in the correct order
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const expected = [
+ "get fields.day",
+ "get fields.day.valueOf",
+ "call fields.day.valueOf",
+ "get fields.era",
+ "get fields.era.toString",
+ "call fields.era.toString",
+ "get fields.eraYear",
+ "get fields.eraYear.valueOf",
+ "call fields.eraYear.valueOf",
+ "get fields.month",
+ "get fields.month.valueOf",
+ "call fields.month.valueOf",
+ "get fields.monthCode",
+ "get fields.monthCode.toString",
+ "call fields.monthCode.toString",
+ "get fields.year",
+ "get fields.year.valueOf",
+ "call fields.year.valueOf",
+ "get options.overflow",
+ "get options.overflow.toString",
+ "call options.overflow.toString",
+];
+const actual = [];
+
+const instance = new Temporal.Calendar("gregory");
+
+const fields = {
+ era: "ce",
+ eraYear: 1.7,
+ year: 1.7,
+ month: 1.7,
+ monthCode: "M01",
+ day: 1.7,
+};
+const arg1 = new Proxy(fields, {
+ get(target, key) {
+ actual.push(`get fields.${key}`);
+ if (key === "calendar") return instance;
+ const result = target[key];
+ return TemporalHelpers.toPrimitiveObserver(actual, result, `fields.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has fields.${key}`);
+ return key in target;
+ },
+});
+
+const options = {
+ overflow: "reject",
+};
+const arg2 = new Proxy(options, {
+ get(target, key) {
+ actual.push(`get options.${key}`);
+ return TemporalHelpers.toPrimitiveObserver(actual, target[key], `options.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has options.${key}`);
+ return key in target;
+ },
+});
+
+const result = instance.monthDayFromFields(arg1, arg2);
+TemporalHelpers.assertPlainMonthDay(result, "M01", 1, "monthDay result");
+assert.sameValue(result.getISOFields().calendar, "gregory", "calendar slot should store a string");
+assert.compareArray(actual, expected, "order of operations");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/reference-year-1972.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/reference-year-1972.js
new file mode 100644
index 0000000000..3dc69433a2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/reference-year-1972.js
@@ -0,0 +1,79 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.monthdayfromfields
+description: Deterministic choosing of the reference year
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const gregory = new Temporal.Calendar("gregory");
+
+const result1 = gregory.monthDayFromFields({ year: 2021, monthCode: "M02", day: 29 });
+TemporalHelpers.assertPlainMonthDay(
+ result1, "M02", 29,
+ "year is ignored and reference year should be 1972 if monthCode is given",
+ 1972
+);
+
+const result2 = gregory.monthDayFromFields({ year: 2021, month: 2, day: 29 }, { overflow: "constrain" });
+TemporalHelpers.assertPlainMonthDay(
+ result2, "M02", 28,
+ "if monthCode is not given, year is used to determine if calendar date exists, but reference year should still be 1972",
+ 1972
+);
+
+assert.throws(
+ RangeError,
+ () => gregory.monthDayFromFields({ year: 2021, month: 2, day: 29 }, { overflow: "reject" }),
+ "RangeError thrown if calendar date does not exist in given year and overflow is reject"
+);
+
+const hebrew = new Temporal.Calendar("hebrew");
+
+const result3 = hebrew.monthDayFromFields({ monthCode: "M01", day: 1 });
+TemporalHelpers.assertPlainMonthDay(
+ result3, "M01", 1,
+ "reference year should be 1972 if date exists in 1972",
+ 1972
+);
+
+const result4 = hebrew.monthDayFromFields({ monthCode: "M05L", day: 1 });
+TemporalHelpers.assertPlainMonthDay(
+ result4, "M05L", 1,
+ "reference year should be the latest ISO year before 1972 if date does not exist in 1972",
+ 1970
+);
+
+const result5 = hebrew.monthDayFromFields({ year: 5781, monthCode: "M02", day: 30 });
+TemporalHelpers.assertPlainMonthDay(
+ result5, "M02", 30,
+ "year is ignored if monthCode is given (Cheshvan 5781 has 29 days)",
+ 1971
+);
+
+const result6 = hebrew.monthDayFromFields({ year: 5781, month: 2, day: 30 }, { overflow: "constrain" });
+TemporalHelpers.assertPlainMonthDay(
+ result6, "M02", 29,
+ "if monthCode is not given, year is used to determine if calendar date exists, but reference year still correct",
+ 1972
+);
+
+assert.throws(
+ RangeError,
+ () => hebrew.monthDayFromFields({ year: 5781, month: 2, day: 30 }, { overflow: "reject" }),
+ "RangeError thrown if calendar date does not exist in given year and overflow is reject"
+);
+
+const result7 = hebrew.monthDayFromFields({ monthCode: "M04", day: 26 });
+TemporalHelpers.assertPlainMonthDay(
+ result7, "M04", 26,
+ "reference date should be the later one, if two options exist in ISO year 1972",
+ 1972
+);
+assert.sameValue(result7.getISOFields().isoMonth, 12, "reference date should be 1972-12-31");
+assert.sameValue(result7.getISOFields().isoDay, 31, "reference date should be 1972-12-31");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthDayFromFields/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..0962de7688
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.monthsinyear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.monthsInYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.monthsInYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/monthsInYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..fe6de21dd7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.weekofyear
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.weekOfYear({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.weekOfYear({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/weekOfYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..193aa77241
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.year
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.year({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.year({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/year/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..f79cf04eb0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/infinity-throws-rangeerror.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.yearmonthfromfields
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => instance.yearMonthFromFields({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.yearMonthFromFields({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/one-of-era-erayear-undefined.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/one-of-era-erayear-undefined.js
new file mode 100644
index 0000000000..ab53618be8
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/one-of-era-erayear-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearmonthfromfields
+description: Throw a TypeError if only one of era/eraYear fields is present
+features: [Temporal]
+---*/
+
+const base = { year: 2000, month: 5, day: 2, era: 'ce' };
+const instance = new Temporal.Calendar('gregory');
+assert.throws(TypeError, () => {
+ instance.yearMonthFromFields({ ...base });
+});
+
+const base2 = { year: 2000, month: 5, day: 2, eraYear: 1 };
+assert.throws(TypeError, () => {
+ instance.yearMonthFromFields({ ...base2 });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/order-of-operations.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/order-of-operations.js
new file mode 100644
index 0000000000..64f82d9524
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/order-of-operations.js
@@ -0,0 +1,75 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearmonthfromfields
+description: Properties on objects passed to yearMonthFromFields() are accessed in the correct order
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const expected = [
+ "get fields.era",
+ "get fields.era.toString",
+ "call fields.era.toString",
+ "get fields.eraYear",
+ "get fields.eraYear.valueOf",
+ "call fields.eraYear.valueOf",
+ "get fields.month",
+ "get fields.month.valueOf",
+ "call fields.month.valueOf",
+ "get fields.monthCode",
+ "get fields.monthCode.toString",
+ "call fields.monthCode.toString",
+ "get fields.year",
+ "get fields.year.valueOf",
+ "call fields.year.valueOf",
+ "get options.overflow",
+ "get options.overflow.toString",
+ "call options.overflow.toString",
+];
+const actual = [];
+
+const instance = new Temporal.Calendar("gregory");
+
+const fields = {
+ era: "ce",
+ eraYear: 1.7,
+ year: 1.7,
+ month: 1.7,
+ monthCode: "M01",
+};
+const arg1 = new Proxy(fields, {
+ get(target, key) {
+ actual.push(`get fields.${key}`);
+ if (key === "calendar") return instance;
+ const result = target[key];
+ return TemporalHelpers.toPrimitiveObserver(actual, result, `fields.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has fields.${key}`);
+ return key in target;
+ },
+});
+
+const options = {
+ overflow: "reject",
+};
+const arg2 = new Proxy(options, {
+ get(target, key) {
+ actual.push(`get options.${key}`);
+ return TemporalHelpers.toPrimitiveObserver(actual, target[key], `options.${key}`);
+ },
+ has(target, key) {
+ actual.push(`has options.${key}`);
+ return key in target;
+ },
+});
+
+const result = instance.yearMonthFromFields(arg1, arg2);
+TemporalHelpers.assertPlainYearMonth(result, 1, 1, "M01", "yearMonth result", "ce", 1);
+assert.sameValue(result.getISOFields().calendar, "gregory", "calendar slot should store a string");
+assert.compareArray(actual, expected, "order of operations");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/reference-day.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/reference-day.js
new file mode 100644
index 0000000000..a2b709ff40
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/reference-day.js
@@ -0,0 +1,164 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.calendar.prototype.yearmonthfromfields
+description: Reference ISO day is chosen to be the first of the calendar month
+info: |
+ 6.d. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, *1*<sub>𝔽</sub>).
+ e. Let _result_ be ? CalendarDateToISO(_calendar_.[[Identifier]], _fields_, _options_).
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const gregory = new Temporal.Calendar("gregory");
+
+const result1 = gregory.yearMonthFromFields({ year: 2023, monthCode: "M01", day: 13 });
+TemporalHelpers.assertPlainYearMonth(
+ result1,
+ 2023, 1, "M01",
+ "reference day is 1 even if day is given",
+ "ce", 2023, /* reference day = */ 1
+);
+
+const result2 = gregory.yearMonthFromFields({ year: 2021, monthCode: "M02", day: 50 }, { overflow: "constrain" });
+TemporalHelpers.assertPlainYearMonth(
+ result2,
+ 2021, 2, "M02",
+ "reference day is set correctly even if day is out of range (overflow constrain)",
+ "ce", 2021, /* reference day = */ 1
+);
+
+const result3 = gregory.yearMonthFromFields({ year: 2021, monthCode: "M02", day: 50 }, { overflow: "reject" });
+TemporalHelpers.assertPlainYearMonth(
+ result3,
+ 2021, 2, "M02",
+ "reference day is set correctly even if day is out of range (overflow reject)",
+ "ce", 2021, /* reference day = */ 1
+);
+
+const hebrew = new Temporal.Calendar("hebrew");
+
+const result4 = hebrew.yearMonthFromFields({ year: 5782, monthCode: "M04", day: 20 });
+TemporalHelpers.assertPlainYearMonth(
+ result4,
+ 5782, 4, "M04",
+ "reference day is the first of the calendar month even if day is given",
+ /* era = */ undefined, /* era year = */ undefined, /* reference day = */ 5
+);
+const isoFields = result4.getISOFields();
+assert.sameValue(isoFields.isoYear, 2021, "Tevet 5782 begins in ISO year 2021");
+assert.sameValue(isoFields.isoMonth, 12, "Tevet 5782 begins in ISO month 12");
+
+const result5 = hebrew.yearMonthFromFields({ year: 5783, monthCode: "M05L" }, { overflow: "constrain" });
+TemporalHelpers.assertPlainYearMonth(
+ result5,
+ 5783, 6, "M06",
+ "month code M05L does not exist in year 5783 (overflow constrain); Hebrew calendar constrains Adar I to Adar",
+ /* era = */ undefined, /* era year = */ undefined, /* reference day = */ 22
+);
+
+assert.throws(
+ RangeError,
+ () => hebrew.yearMonthFromFields({ year: 5783, monthCode: "M05L" }, { overflow: "reject" }),
+ "month code M05L does not exist in year 5783 (overflow reject)",
+);
+
+const result6 = hebrew.yearMonthFromFields({ year: 5783, month: 13 }, { overflow: "constrain" });
+TemporalHelpers.assertPlainYearMonth(
+ result6,
+ 5783, 12, "M12",
+ "month 13 does not exist in year 5783 (overflow constrain)",
+ /* era = */ undefined, /* era year = */ undefined, /* reference day = */ 18
+);
+
+assert.throws(
+ RangeError,
+ () => hebrew.yearMonthFromFields({ year: 5783, month: 13 }, { overflow: "reject" }),
+ "month 13 does not exist in year 5783 (overflow reject)",
+);
+
+const result7 = hebrew.yearMonthFromFields({ year: 5782, monthCode: "M04", day: 50 }, { overflow: "constrain" });
+TemporalHelpers.assertPlainYearMonth(
+ result7,
+ 5782, 4, "M04",
+ "reference day is set correctly even if day is out of range (overflow constrain)",
+ /* era = */ undefined, /* era year = */ undefined, /* reference day = */ 5
+);
+
+const result8 = hebrew.yearMonthFromFields({ year: 5782, monthCode: "M04", day: 50 }, { overflow: "reject" });
+TemporalHelpers.assertPlainYearMonth(
+ result8,
+ 5782, 4, "M04",
+ "reference day is set correctly even if day is out of range (overflow reject)",
+ /* era = */ undefined, /* era year = */ undefined, /* reference day = */ 5
+);
+
+const chinese = new Temporal.Calendar("chinese");
+
+// Month codes, month indices, and the ISO reference days of the months in 2022
+const months2022TestData = [
+ // TODO: Sources conflict over whether M01L and M12L exist in _any_ year.
+ // Clarify this, and delete if appropriate. ICU has them, but may be wrong.
+ ["M01", 1, 1],
+ ["M02", 2, 3],
+ ["M03", 3, 1],
+ ["M04", 4, 1],
+ ["M05", 5, 30],
+ ["M06", 6, 29],
+ ["M07", 7, 29],
+ ["M08", 8, 27],
+ ["M09", 9, 26],
+ ["M10", 10, 25],
+ ["M11", 11, 24],
+ ["M12", 12, 23],
+];
+for (const [nonLeapMonthCode, month, referenceISODay] of months2022TestData) {
+ const leapMonthCode = nonLeapMonthCode + "L";
+ const fields = { year: 2022, monthCode: leapMonthCode };
+
+ const result = chinese.yearMonthFromFields(fields, { overflow: "constrain" });
+ TemporalHelpers.assertPlainYearMonth(
+ result,
+ 2022, month, nonLeapMonthCode,
+ `Chinese intercalary month ${leapMonthCode} does not exist in year 2022 (overflow constrain)`,
+ /* era = */ undefined, /* era year = */ undefined, referenceISODay
+ );
+
+ assert.throws(
+ RangeError,
+ () => chinese.yearMonthFromFields(fields, { overflow: "reject" }),
+ `Chinese intercalary month ${leapMonthCode} does not exist in year 2022 (overflow reject)`
+ );
+}
+
+// Years in which leap months exist according to ICU
+const leapMonthsTestData = [
+ ["M01L", 2148, 2, 20],
+ ["M02L", 2023, 3, 22],
+ ["M03L", 1993, 4, 22],
+ ["M04L", 2020, 5, 23],
+ ["M05L", 2009, 6, 23],
+ ["M06L", 2017, 7, 23],
+ ["M07L", 2006, 8, 24],
+ ["M08L", 1995, 9, 25],
+ ["M09L", 2014, 10, 24],
+ ["M10L", 1984, 11, 23],
+ ["M11L", 2033, 12, 22],
+ ["M12L", 1889, 13, 21, 1890, 1],
+];
+for (const [monthCode, year, month, referenceISODay, isoYear = year, isoMonth = month] of leapMonthsTestData) {
+ const result = chinese.yearMonthFromFields({ year, monthCode });
+ TemporalHelpers.assertPlainYearMonth(
+ result,
+ year, month, monthCode,
+ `Date of sample Chinese intercalary month ${monthCode}`,
+ /* era = */ undefined, /* era year = */ undefined, referenceISODay
+ );
+ const isoFields = result.getISOFields();
+ assert.sameValue(isoFields.isoYear, isoYear, `${year}-${monthCode} starts in ISO year ${isoYear}`);
+ assert.sameValue(isoFields.isoMonth, isoMonth, `${year}-${monthCode} starts in ISO month ${isoMonth}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearMonthFromFields/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/browser.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..d8be8ffea1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.calendar.prototype.yearofweek
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Calendar("gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.yearOfWeek({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.yearOfWeek({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/prototype/yearOfWeek/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Calendar/shell.js b/js/src/tests/test262/intl402/Temporal/Calendar/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Calendar/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/compare/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-hour.js b/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-hour.js
new file mode 100644
index 0000000000..678252ad2a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-hour.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.compare
+description: relativeTo with hours.
+features: [Temporal]
+---*/
+
+const oneDay = new Temporal.Duration(0, 0, 0, 1);
+const hours24 = new Temporal.Duration(0, 0, 0, 0, 24);
+
+assert.sameValue(
+ Temporal.Duration.compare(oneDay, hours24, { relativeTo: Temporal.ZonedDateTime.from('2017-01-01T00:00[America/Montevideo]') }),
+ 0,
+ 'relativeTo does not affect days if ZonedDateTime, and duration encompasses no DST change');
+assert.sameValue(
+ Temporal.Duration.compare(oneDay, hours24, { relativeTo: Temporal.ZonedDateTime.from('2019-11-03T00:00[America/Vancouver]') }),
+ 1,
+ 'relativeTo does affect days if ZonedDateTime, and duration encompasses DST change');
+assert.sameValue(
+ Temporal.Duration.compare(oneDay, hours24, { relativeTo: '2019-11-03T00:00[America/Vancouver]' }),
+ 1,
+ 'casts relativeTo to ZonedDateTime from string');
+assert.sameValue(
+ Temporal.Duration.compare(oneDay, hours24, {
+ relativeTo: { year: 2019, month: 11, day: 3, timeZone: 'America/Vancouver' }
+ }),
+ 1,
+ 'casts relativeTo to ZonedDateTime from object');
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-sub-minute-offset.js
new file mode 100644
index 0000000000..da46868524
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/compare/relativeto-sub-minute-offset.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.compare
+description: relativeTo string accepts an inexact UTC offset rounded to hours and minutes
+features: [Temporal]
+---*/
+
+const duration1 = new Temporal.Duration(0, 0, 0, 31);
+const duration2 = new Temporal.Duration(0, 1);
+
+let result;
+let relativeTo;
+
+const action = (relativeTo) => Temporal.Duration.compare(duration1, duration2, { relativeTo });
+
+relativeTo = "1970-01-01T00:00:00-00:45[Africa/Monrovia]";
+result = action(relativeTo);
+assert.sameValue(result, 0, "rounded HH:MM is accepted in string offset");
+
+relativeTo = "1970-01-01T00:00:00-00:44:30[Africa/Monrovia]";
+result = action(relativeTo);
+assert.sameValue(result, 0, "unrounded HH:MM:SS is accepted in string offset");
+
+const timeZone = Temporal.TimeZone.from("Africa/Monrovia");
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45", timeZone };
+assert.throws(RangeError, () => action(relativeTo), "rounded HH:MM not accepted as offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/compare/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/compare/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/compare/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..83bf1b50ef
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.duration.prototype.add
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 987, 654, 321);
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.add(instance, { relativeTo: { ...base, eraYear: inf } }), `eraYear property cannot be ${inf} in relativeTo`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.add(instance, { relativeTo: { ...base, eraYear: obj } }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-string-datetime.js
new file mode 100644
index 0000000000..752b920126
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-string-datetime.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: >
+ Conversion of ISO date-time strings as relativeTo option to
+ Temporal.ZonedDateTime or Temporal.PlainDateTime instances
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+
+let relativeTo = "2019-11-01T00:00[America/Vancouver]";
+const result4 = instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo });
+TemporalHelpers.assertDuration(result4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00Z[America/Vancouver]";
+const result5 = instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo });
+TemporalHelpers.assertDuration(result5, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + Z + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00-07:00[America/Vancouver]";
+const result6 = instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo });
+TemporalHelpers.assertDuration(result6, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + offset + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00+04:15[America/Vancouver]";
+assert.throws(RangeError, () => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo }), "date-time + offset + IANA annotation throws if wall time and exact time mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-sub-minute-offset.js
new file mode 100644
index 0000000000..8ad6ef73f5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/relativeto-sub-minute-offset.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.add
+description: relativeTo string accepts an inexact UTC offset rounded to hours and minutes
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+let result;
+let relativeTo;
+const action = (relativeTo) => instance.add(new Temporal.Duration(0, 0, 0, 0, -24), { relativeTo });
+
+relativeTo = "1970-01-01T00:00-00:45:00[-00:45]";
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 0, "ISO string offset accepted with zero seconds (string)");
+
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45:00.000000000", timeZone: "+00:45" };
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 0, "ISO string offset accepted with zero seconds (property bag)");
+
+relativeTo = "1970-01-01T00:00+00:44:30.123456789[+00:45]";
+assert.throws(RangeError, () => action(relativeTo), "rounding is not accepted between ISO offset and time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/add/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..2561e27221
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.duration.prototype.round
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 987, 654, 321);
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.round({ smallestUnit: "seconds", relativeTo: { ...base, eraYear: inf } }), `eraYear property cannot be ${inf} in relativeTo`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.round({ smallestUnit: "seconds", relativeTo: { ...base, eraYear: obj } }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-string-datetime.js
new file mode 100644
index 0000000000..691a40ad9e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-string-datetime.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: >
+ Conversion of ISO date-time strings as relativeTo option to
+ Temporal.ZonedDateTime or Temporal.PlainDateTime instances
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+
+let relativeTo = "2019-11-01T00:00[America/Vancouver]";
+const result4 = instance.round({ largestUnit: "years", relativeTo });
+TemporalHelpers.assertDuration(result4, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, "date-time + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00Z[America/Vancouver]";
+const result5 = instance.round({ largestUnit: "years", relativeTo });
+TemporalHelpers.assertDuration(result5, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, "date-time + Z + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00-07:00[America/Vancouver]";
+const result6 = instance.round({ largestUnit: "years", relativeTo });
+TemporalHelpers.assertDuration(result6, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, "date-time + offset + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00+04:15[America/Vancouver]";
+assert.throws(RangeError, () => instance.round({ largestUnit: "years", relativeTo }), "date-time + offset + IANA annotation throws if wall time and exact time mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-sub-minute-offset.js
new file mode 100644
index 0000000000..19875672b1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/relativeto-sub-minute-offset.js
@@ -0,0 +1,42 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.round
+description: relativeTo string accepts an inexact UTC offset rounded to hours and minutes
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+
+let result;
+let relativeTo;
+
+const action = (relativeTo) => instance.round({ largestUnit: "years", relativeTo });
+
+relativeTo = "1970-01-01T00:00-00:45:00[-00:45]";
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 1, "ISO string offset accepted with zero seconds (string)");
+
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45:00.000000000", timeZone: "+00:45" };
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 1, "ISO string offset accepted with zero seconds (property bag)");
+
+relativeTo = "1970-01-01T00:00:00-00:45[Africa/Monrovia]";
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 1, "rounded HH:MM is accepted in string offset");
+
+relativeTo = "1970-01-01T00:00:00-00:44:30[Africa/Monrovia]";
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 1, "unrounded HH:MM:SS is accepted in string offset");
+
+relativeTo = "1970-01-01T00:00+00:44:30.123456789[+00:45]";
+assert.throws(RangeError, () => action(relativeTo), "rounding is not accepted between ISO offset and time zone");
+
+const timeZone = Temporal.TimeZone.from("Africa/Monrovia");
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45", timeZone };
+assert.throws(RangeError, () => action(relativeTo), "rounded HH:MM not accepted as offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/round/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..bc74500a01
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.duration.prototype.subtract
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 987, 654, 321);
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.subtract(instance, { relativeTo: { ...base, eraYear: inf } }), `eraYear property cannot be ${inf} in relativeTo`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.subtract(instance, { relativeTo: { ...base, eraYear: obj } }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-string-datetime.js
new file mode 100644
index 0000000000..c04ca96e3b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-string-datetime.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: >
+ Conversion of ISO date-time strings as relativeTo option to
+ Temporal.ZonedDateTime or Temporal.PlainDateTime instances
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+
+let relativeTo = "2019-11-01T00:00[America/Vancouver]";
+const result4 = instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo });
+TemporalHelpers.assertDuration(result4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00Z[America/Vancouver]";
+const result5 = instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo });
+TemporalHelpers.assertDuration(result5, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + Z + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00-07:00[America/Vancouver]";
+const result6 = instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo });
+TemporalHelpers.assertDuration(result6, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, "date-time + offset + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00+04:15[America/Vancouver]";
+assert.throws(RangeError, () => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo }), "date-time + offset + IANA annotation throws if wall time and exact time mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-sub-minute-offset.js
new file mode 100644
index 0000000000..bea613cd5d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/relativeto-sub-minute-offset.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.subtract
+description: relativeTo string accepts an inexact UTC offset rounded to hours and minutes
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 1);
+
+let result;
+let relativeTo;
+const action = (relativeTo) => instance.subtract(new Temporal.Duration(0, 0, 0, 0, 24), { relativeTo });
+
+relativeTo = "1970-01-01T00:00-00:45:00[-00:45]";
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 0, "ISO string offset accepted with zero seconds (string)");
+
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45:00.000000000", timeZone: "+00:45" };
+result = action(relativeTo);
+TemporalHelpers.assertDateDuration(result, 1, 0, 0, 0, "ISO string offset accepted with zero seconds (property bag)");
+
+relativeTo = "1970-01-01T00:00+00:44:30.123456789[+00:45]";
+assert.throws(RangeError, () => action(relativeTo), "rounding is not accepted between ISO offset and time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/subtract/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/browser.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..fd500c83a1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.duration.prototype.total
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 987, 654, 321);
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.total({ unit: "seconds", relativeTo: { ...base, eraYear: inf } }), `eraYear property cannot be ${inf} in relativeTo`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.total({ unit: "seconds", relativeTo: { ...base, eraYear: obj } }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-string-datetime.js
new file mode 100644
index 0000000000..be00a9ed23
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-string-datetime.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: >
+ Conversion of ISO date-time strings as relativeTo option to
+ Temporal.ZonedDateTime or Temporal.PlainDateTime instances
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+
+let relativeTo = "2019-11-01T00:00[America/Vancouver]";
+const result4 = instance.total({ unit: "days", relativeTo });
+assert.sameValue(result4, 366.96, "date-time + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00Z[America/Vancouver]";
+const result5 = instance.total({ unit: "days", relativeTo });
+assert.sameValue(result5, 366.96, "date-time + Z + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00-07:00[America/Vancouver]";
+const result6 = instance.total({ unit: "days", relativeTo });
+assert.sameValue(result6, 366.96, "date-time + offset + IANA annotation is a zoned relativeTo");
+
+relativeTo = "2019-11-01T00:00+04:15[America/Vancouver]";
+assert.throws(RangeError, () => instance.total({ unit: "days", relativeTo }), "date-time + offset + IANA annotation throws if wall time and exact time mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-sub-minute-offset.js
new file mode 100644
index 0000000000..92c3b5af79
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/relativeto-sub-minute-offset.js
@@ -0,0 +1,41 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.duration.prototype.total
+description: relativeTo string accepts an inexact UTC offset rounded to hours and minutes
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Duration(1, 0, 0, 0, 24);
+
+let result;
+let relativeTo;
+
+const action = (relativeTo) => instance.total({ unit: "days", relativeTo });
+
+relativeTo = "1970-01-01T00:00-00:45:00[-00:45]";
+result = action(relativeTo);
+assert.sameValue(result, 366, "ISO string offset accepted with zero seconds (string)");
+
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45:00.000000000", timeZone: "+00:45" };
+result = action(relativeTo);
+assert.sameValue(result, 366, "ISO string offset accepted with zero seconds (property bag)");
+
+relativeTo = "1970-01-01T00:00:00-00:45[Africa/Monrovia]";
+result = action(relativeTo);
+assert.sameValue(result, 366, "rounded HH:MM is accepted in string offset");
+
+relativeTo = "1970-01-01T00:00:00-00:44:30[Africa/Monrovia]";
+result = action(relativeTo);
+assert.sameValue(result, 366, "unrounded HH:MM:SS is accepted in string offset");
+
+relativeTo = "1970-01-01T00:00+00:44:30.123456789[+00:45]";
+assert.throws(RangeError, () => action(relativeTo), "rounding is not accepted between ISO offset and time zone");
+
+const timeZone = Temporal.TimeZone.from("Africa/Monrovia");
+relativeTo = { year: 1970, month: 1, day: 1, offset: "+00:45", timeZone };
+assert.throws(RangeError, () => action(relativeTo), "rounded HH:MM not accepted as offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/prototype/total/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/Duration/shell.js b/js/src/tests/test262/intl402/Temporal/Duration/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Duration/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..a111b53390
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [BigInt, Temporal]
+---*/
+
+const instant = new Temporal.Instant(957270896_987_650_000n);
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const expected = defaultFormatter.format(instant);
+
+const actualExplicit = instant.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = instant.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..ec694466cf
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.instant.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [BigInt, Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "fractionalSecondDigits", 3 ],
+];
+const instant = new Temporal.Instant(957270896_987_650_000n);
+
+assert.sameValue(typeof instant.toLocaleString("en", { dateStyle: "short" }), "string");
+assert.sameValue(typeof instant.toLocaleString("en", { timeStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ instant.toLocaleString("en", { [option]: value, dateStyle: "short" });
+ }, `instant.toLocaleString("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+
+ assert.throws(TypeError, function() {
+ instant.toLocaleString("en", { [option]: value, timeStyle: "short" });
+ }, `instant.toLocaleString("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..50a69ef1b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [BigInt, Temporal]
+---*/
+
+const instant = new Temporal.Instant(957270896_987_650_000n);
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const expected = defaultFormatter.format(instant);
+
+const actualExplicit = instant.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = instant.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-offset.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-offset.js
new file mode 100644
index 0000000000..927a2ff6c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-offset.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tostring
+description: The time zone offset part of the string serialization (IANA time zones)
+features: [BigInt, Temporal]
+---*/
+
+const instant = new Temporal.Instant(0n);
+
+function test(timeZoneIdentifier, expected, description) {
+ const timeZone = new Temporal.TimeZone(timeZoneIdentifier);
+ assert.sameValue(instant.toString({ timeZone }), expected, description);
+}
+
+test("Europe/Berlin", "1970-01-01T01:00:00+01:00", "positive offset");
+test("America/New_York", "1969-12-31T19:00:00-05:00", "negative offset");
+test("Africa/Monrovia", "1969-12-31T23:15:30-00:45", "sub-minute offset");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-string-datetime.js
new file mode 100644
index 0000000000..f25a7e147f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toString/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tostring
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Instant(0n);
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toString({ timeZone });
+assert.sameValue(result1.substr(-6), "-08:00", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toString({ timeZone });
+assert.sameValue(result2.substr(-6), "-08:00", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toString({ timeZone });
+assert.sameValue(result3.substr(-6), "-08:00", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/timezone-string-datetime.js
new file mode 100644
index 0000000000..cb854630ff
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTime/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tozoneddatetime
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Instant(0n);
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toZonedDateTime({ timeZone, calendar: "iso8601" });
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toZonedDateTime({ timeZone, calendar: "iso8601" });
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toZonedDateTime({ timeZone, calendar: "iso8601" });
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/browser.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-string-datetime.js
new file mode 100644
index 0000000000..7f141cb1ec
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.instant.prototype.tozoneddatetimeiso
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.Instant(0n);
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toZonedDateTimeISO(timeZone);
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toZonedDateTimeISO(timeZone);
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toZonedDateTimeISO(timeZone);
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Instant/shell.js b/js/src/tests/test262/intl402/Temporal/Instant/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Instant/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/browser.js b/js/src/tests/test262/intl402/Temporal/Now/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDate/browser.js b/js/src/tests/test262/intl402/Temporal/Now/plainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDate/calendar-string.js b/js/src/tests/test262/intl402/Temporal/Now/plainDate/calendar-string.js
new file mode 100644
index 0000000000..b454ed12dd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDate/calendar-string.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.plaindate
+description: String calendar argument
+features: [Temporal]
+---*/
+
+const date = Temporal.Now.plainDate("gregory");
+assert(date instanceof Temporal.PlainDate);
+assert.sameValue(date.calendarId, "gregory");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDate/shell.js b/js/src/tests/test262/intl402/Temporal/Now/plainDate/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDate/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/calendar-string.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/calendar-string.js
new file mode 100644
index 0000000000..207269c56b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/calendar-string.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.plaindatetime
+description: String calendar argument
+features: [Temporal]
+---*/
+
+const dt = Temporal.Now.plainDateTime("gregory");
+assert(dt instanceof Temporal.PlainDateTime);
+assert.sameValue(dt.calendarId, "gregory");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/browser.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/shell.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/timezone-string-datetime.js
new file mode 100644
index 0000000000..41a9ddb98c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/plainDateTimeISO/timezone-string-datetime.js
@@ -0,0 +1,63 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-temporal.now.plaindatetimeiso
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30";
+assert.throws(RangeError, () => Temporal.Now.plainDateTimeISO(timeZone), "bare date-time string is not a time zone");
+
+[
+ "2021-08-19T17:30-07:00:01",
+ "2021-08-19T17:30-07:00:00",
+ "2021-08-19T17:30-07:00:00.1",
+ "2021-08-19T17:30-07:00:00.0",
+ "2021-08-19T17:30-07:00:00.01",
+ "2021-08-19T17:30-07:00:00.00",
+ "2021-08-19T17:30-07:00:00.001",
+ "2021-08-19T17:30-07:00:00.000",
+ "2021-08-19T17:30-07:00:00.0001",
+ "2021-08-19T17:30-07:00:00.0000",
+ "2021-08-19T17:30-07:00:00.00001",
+ "2021-08-19T17:30-07:00:00.00000",
+ "2021-08-19T17:30-07:00:00.000001",
+ "2021-08-19T17:30-07:00:00.000000",
+ "2021-08-19T17:30-07:00:00.0000001",
+ "2021-08-19T17:30-07:00:00.0000000",
+ "2021-08-19T17:30-07:00:00.00000001",
+ "2021-08-19T17:30-07:00:00.00000000",
+ "2021-08-19T17:30-07:00:00.000000001",
+ "2021-08-19T17:30-07:00:00.000000000",
+].forEach((timeZone) => {
+ assert.throws(
+ RangeError,
+ () => Temporal.Now.plainDateTimeISO(timeZone),
+ `ISO string ${timeZone} with a sub-minute offset is not a valid time zone`
+ );
+});
+
+// The following are all valid strings so should not throw:
+
+[
+ "2021-08-19T17:30Z",
+ "2021-08-19T1730Z",
+ "2021-08-19T17:30-07:00",
+ "2021-08-19T1730-07:00",
+ "2021-08-19T17:30-0700",
+ "2021-08-19T1730-0700",
+ "2021-08-19T17:30[America/Vancouver]",
+ "2021-08-19T1730[America/Vancouver]",
+ "2021-08-19T17:30Z[America/Vancouver]",
+ "2021-08-19T1730Z[America/Vancouver]",
+ "2021-08-19T17:30-07:00[America/Vancouver]",
+ "2021-08-19T1730-07:00[America/Vancouver]",
+ "2021-08-19T17:30-0700[America/Vancouver]",
+ "2021-08-19T1730-0700[America/Vancouver]",
+].forEach((timeZone) => {
+ Temporal.Now.plainDateTimeISO(timeZone);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/shell.js b/js/src/tests/test262/intl402/Temporal/Now/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-string.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-string.js
new file mode 100644
index 0000000000..a8e94a4567
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-string.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetime
+description: String calendar argument
+features: [Temporal]
+---*/
+
+const zdt = Temporal.Now.zonedDateTime("gregory");
+const tz = Temporal.Now.timeZoneId();
+assert(zdt instanceof Temporal.ZonedDateTime);
+assert.sameValue(typeof zdt.getISOFields().calendar, "string", "calendar slot should store a string");
+assert.sameValue(zdt.calendarId, "gregory");
+assert.sameValue(typeof zdt.getISOFields().timeZone, "string", "time zone slot should store a string");
+assert.sameValue(zdt.timeZoneId, tz);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-timezone-string.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-timezone-string.js
new file mode 100644
index 0000000000..a97baba9b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/calendar-timezone-string.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetime
+description: String calendar and time zone arguments
+features: [Temporal]
+---*/
+
+const zdt = Temporal.Now.zonedDateTime("gregory", "America/Los_Angeles");
+assert(zdt instanceof Temporal.ZonedDateTime);
+assert.sameValue(typeof zdt.getISOFields().calendar, "string", "calendar slot should store a string");
+assert.sameValue(zdt.calendarId, "gregory");
+assert.sameValue(typeof zdt.getISOFields().timeZone, "string", "time zone slot should store a string");
+assert.sameValue(zdt.timeZoneId, "America/Los_Angeles");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/timezone-string-datetime.js
new file mode 100644
index 0000000000..90b8859b0f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTime/timezone-string-datetime.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetime
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.Now.zonedDateTime("iso8601", timeZone);
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = Temporal.Now.zonedDateTime("iso8601", timeZone);
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = Temporal.Now.zonedDateTime("iso8601", timeZone);
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/browser.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/shell.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string-datetime.js
new file mode 100644
index 0000000000..ba406fe2a5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string-datetime.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetimeiso
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.Now.zonedDateTimeISO(timeZone);
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = Temporal.Now.zonedDateTimeISO(timeZone);
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = Temporal.Now.zonedDateTimeISO(timeZone);
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string.js b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string.js
new file mode 100644
index 0000000000..85b7d58b03
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/Now/zonedDateTimeISO/timezone-string.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.now.zoneddatetimeiso
+description: String calendar argument
+features: [Temporal]
+---*/
+
+const zdt = Temporal.Now.zonedDateTimeISO("America/Los_Angeles");
+assert(zdt instanceof Temporal.ZonedDateTime);
+assert.sameValue(typeof zdt.getISOFields().calendar, "string", "calendar slot should store a string");
+assert.sameValue(zdt.calendarId, "iso8601");
+assert.sameValue(typeof zdt.getISOFields().timeZone, "string", "time zone slot should store a string");
+assert.sameValue(zdt.timeZoneId, "America/Los_Angeles");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/compare/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/compare/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..dd90b5bd85
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/infinity-throws-rangeerror.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.plaindate.compare
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const other = new Temporal.PlainDate(2000, 5, 2, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => Temporal.PlainDate.compare({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => Temporal.PlainDate.compare(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDate.compare({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDate.compare(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/compare/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/compare/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..c38a5e633e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/from/infinity-throws-rangeerror.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindate.from
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => Temporal.PlainDate.from({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDate.from({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/from/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/from/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/from/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..2d4fac8989
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindate.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/equals/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/branding.js
new file mode 100644
index 0000000000..64a07fd3b2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Object.getOwnPropertyDescriptor(Temporal.PlainDate.prototype, "era").get;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.PlainDate), "Temporal.PlainDate");
+assert.throws(TypeError, () => era.call(Temporal.PlainDate.prototype), "Temporal.PlainDate.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..769f188f70
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.era
+description: The "era" property of Temporal.PlainDate.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainDate.prototype, "era");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/validate-calendar-value.js
new file mode 100644
index 0000000000..d4d0aab391
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/era/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.era
+description: Validate result returned from calendar era() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, TypeError],
+ [-Infinity, TypeError],
+ [NaN, TypeError],
+ [-7, TypeError],
+ [-0.1, TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, TypeError],
+ [{valueOf() { return "7"; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDate(1981, 12, 15, calendar);
+ assert.throws(error, () => instance.era, `${typeof result} ${String(result)} not converted to string`);
+});
+
+const preservedResults = [
+ undefined,
+ "string",
+ "7",
+ "7.5",
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDate(1981, 12, 15, calendar);
+ assert.sameValue(instance.era, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..fd68d2761c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Object.getOwnPropertyDescriptor(Temporal.PlainDate.prototype, "eraYear").get;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainDate), "Temporal.PlainDate");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainDate.prototype), "Temporal.PlainDate.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..1b182c306a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.erayear
+description: The "eraYear" property of Temporal.PlainDate.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainDate.prototype, "eraYear");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/validate-calendar-value.js
new file mode 100644
index 0000000000..48bb01304e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/eraYear/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindate.prototype.erayear
+description: Validate result returned from calendar eraYear() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, RangeError],
+ [-Infinity, RangeError],
+ [NaN, RangeError],
+ [-0.1, RangeError],
+ ["string", TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, RangeError],
+ ["7", TypeError],
+ ["7.5", TypeError],
+ [{valueOf() { return 7; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDate(1981, 12, 15, calendar);
+ assert.throws(error, () => instance.eraYear, `${typeof result} ${String(result)} not converted to integer`);
+});
+
+const preservedResults = [
+ undefined,
+ -7,
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDate(1981, 12, 15, calendar);
+ assert.sameValue(instance.eraYear, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..4df1a71f81
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindate.prototype.since
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/since/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js
new file mode 100644
index 0000000000..14df21c029
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tolocalestring
+description: Calendar must match the locale calendar if not "iso8601"
+features: [Temporal, Intl-enumeration]
+---*/
+
+const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
+assert.notSameValue(localeCalendar, "iso8601", "no locale has the ISO calendar");
+
+const sameCalendarInstance = new Temporal.PlainDate(2000, 5, 2, localeCalendar);
+const result = sameCalendarInstance.toLocaleString();
+assert.sameValue(typeof result, "string", "toLocaleString() succeeds when instance has the same calendar as locale");
+
+const isoInstance = new Temporal.PlainDate(2000, 5, 2, "iso8601");
+assert.sameValue(isoInstance.toLocaleString(), result, "toLocaleString() succeeds when instance has the ISO calendar")
+
+// Pick a different calendar that is not ISO and not the locale's calendar
+const calendars = new Set(Intl.supportedValuesOf("calendar"));
+calendars.delete("iso8601");
+calendars.delete(localeCalendar);
+const differentCalendar = calendars.values().next().value;
+
+const differentCalendarInstance = new Temporal.PlainDate(2000, 5, 2, differentCalendar);
+assert.throws(RangeError, () => differentCalendarInstance.toLocaleString(), "calendar mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..b33b9557f3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [Temporal]
+---*/
+
+const date = new Temporal.PlainDate(2000, 5, 2);
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const expected = defaultFormatter.format(date);
+
+const actualExplicit = date.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = date.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..962d255a4e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.plaindate.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+];
+const date = new Temporal.PlainDate(2000, 5, 2);
+
+assert.sameValue(typeof date.toLocaleString("en", { dateStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ date.toLocaleString("en", { [option]: value, dateStyle: "short" });
+ }, `date.toLocaleString("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..87b5084099
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [Temporal]
+---*/
+
+const date = new Temporal.PlainDate(2000, 5, 2);
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const expected = defaultFormatter.format(date);
+
+const actualExplicit = date.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = date.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/resolved-time-zone.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/resolved-time-zone.js
new file mode 100644
index 0000000000..926728d9e4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/resolved-time-zone.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tolocalestring
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+const date = new Temporal.PlainDate(2021, 8, 4);
+const result = date.toLocaleString("en", { timeZone: "Pacific/Apia" });
+assert.sameValue(result, "8/4/2021");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/timezone-string-datetime.js
new file mode 100644
index 0000000000..74e42258ac
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/toZonedDateTime/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.tozoneddatetime
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2);
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..98495547f3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindate.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDate(2000, 5, 2, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/until/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js
new file mode 100644
index 0000000000..d372a581cf
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/cross-era-boundary.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindate.prototype.with
+description: Behaviour when property bag forms a date out of bounds of the current era
+features: [Temporal]
+---*/
+
+// Last day of Showa era
+const instance = new Temporal.PlainDate(1989, 1, 7, "japanese");
+
+const result1 = instance.with({ day: 10 });
+assert.notSameValue(result1.era, instance.era, "resulting day should have crossed an era boundary");
+
+const result2 = instance.with({ month: 2 });
+assert.notSameValue(result2.era, instance.era, "resulting month should have crossed an era boundary");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/prototype/with/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDate/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDate/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDate/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..43d5dd142a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/infinity-throws-rangeerror.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.compare
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const other = new Temporal.PlainDateTime(2000, 5, 2, 15, 0, 0, 0, 0, 0, "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => Temporal.PlainDateTime.compare({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => Temporal.PlainDateTime.compare(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDateTime.compare({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDateTime.compare(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/compare/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..8dab95abcb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/infinity-throws-rangeerror.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.from
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => Temporal.PlainDateTime.from({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainDateTime.from({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/from/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..d83ac3ad15
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 15, 0, 0, 0, 0, 0, "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/equals/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/branding.js
new file mode 100644
index 0000000000..9dec07bdfd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Object.getOwnPropertyDescriptor(Temporal.PlainDateTime.prototype, "era").get;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.PlainDateTime), "Temporal.PlainDateTime");
+assert.throws(TypeError, () => era.call(Temporal.PlainDateTime.prototype), "Temporal.PlainDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..4565b39f6a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.era
+description: The "era" property of Temporal.PlainDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainDateTime.prototype, "era");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/validate-calendar-value.js
new file mode 100644
index 0000000000..e887eab427
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/era/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.era
+description: Validate result returned from calendar era() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, TypeError],
+ [-Infinity, TypeError],
+ [NaN, TypeError],
+ [-7, TypeError],
+ [-0.1, TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, TypeError],
+ [{valueOf() { return "7"; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDateTime(1981, 12, 15, 14, 15, 45, 987, 654, 321, calendar);
+ assert.throws(error, () => instance.era, `${typeof result} ${String(result)} not converted to string`);
+});
+
+const preservedResults = [
+ undefined,
+ "string",
+ "7",
+ "7.5",
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDateTime(1981, 12, 15, 14, 15, 45, 987, 654, 321, calendar);
+ assert.sameValue(instance.era, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..80fa8e8340
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Object.getOwnPropertyDescriptor(Temporal.PlainDateTime.prototype, "eraYear").get;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainDateTime), "Temporal.PlainDateTime");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainDateTime.prototype), "Temporal.PlainDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..0500021f93
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.erayear
+description: The "eraYear" property of Temporal.PlainDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainDateTime.prototype, "eraYear");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/validate-calendar-value.js
new file mode 100644
index 0000000000..3c32f8d788
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/eraYear/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plaindatetime.prototype.erayear
+description: Validate result returned from calendar eraYear() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, RangeError],
+ [-Infinity, RangeError],
+ [NaN, RangeError],
+ [-0.1, RangeError],
+ ["string", TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, RangeError],
+ ["7", TypeError],
+ ["7.5", TypeError],
+ [{valueOf() { return 7; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDateTime(1981, 12, 15, 14, 15, 45, 987, 654, 321, calendar);
+ assert.throws(error, () => instance.eraYear, `${typeof result} ${String(result)} not converted to integer`);
+});
+
+const preservedResults = [
+ undefined,
+ -7,
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainDateTime(1981, 12, 15, 14, 15, 45, 987, 654, 321, calendar);
+ assert.sameValue(instance.eraYear, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..b1d0140229
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.prototype.since
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 15, 0, 0, 0, 0, 0, "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/since/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js
new file mode 100644
index 0000000000..e50cc4237f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tolocalestring
+description: Calendar must match the locale calendar if not "iso8601"
+features: [Temporal, Intl-enumeration]
+---*/
+
+const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
+assert.notSameValue(localeCalendar, "iso8601", "no locale has the ISO calendar");
+
+const sameCalendarInstance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, localeCalendar);
+const result = sameCalendarInstance.toLocaleString();
+assert.sameValue(typeof result, "string", "toLocaleString() succeeds when instance has the same calendar as locale");
+
+const isoInstance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, "iso8601");
+assert.sameValue(isoInstance.toLocaleString(), result, "toLocaleString() succeeds when instance has the ISO calendar")
+
+// Pick a different calendar that is not ISO and not the locale's calendar
+const calendars = new Set(Intl.supportedValuesOf("calendar"));
+calendars.delete("iso8601");
+calendars.delete(localeCalendar);
+const differentCalendar = calendars.values().next().value;
+
+const differentCalendarInstance = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, differentCalendar);
+assert.throws(RangeError, () => differentCalendarInstance.toLocaleString(), "calendar mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..b9e6b75381
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const expected = defaultFormatter.format(datetime);
+
+const actualExplicit = datetime.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..e0bbf809f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.plaindatetime.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "fractionalSecondDigits", 3 ],
+];
+const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+
+assert.sameValue(typeof datetime.toLocaleString("en", { dateStyle: "short" }), "string");
+assert.sameValue(typeof datetime.toLocaleString("en", { timeStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, dateStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, timeStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..de5988529d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const expected = defaultFormatter.format(datetime);
+
+const actualExplicit = datetime.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/resolved-time-zone.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/resolved-time-zone.js
new file mode 100644
index 0000000000..5c031fca3d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/resolved-time-zone.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tolocalestring
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+// Using 24-hour clock to avoid format differences between Node 19 (which puts
+// "\u{202f}" before AM/PM) and previous versions that use regular spaces.
+const options = {
+ timeZone: "Pacific/Apia",
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ hourCycle: "h23"
+};
+
+const datetime1 = new Temporal.PlainDateTime(2021, 8, 4, 0, 30, 45, 123, 456, 789);
+const result1 = datetime1.toLocaleString("en", options);
+assert.sameValue(result1, "8/4/2021, 00:30:45");
+
+const datetime2 = new Temporal.PlainDateTime(2021, 8, 4, 23, 30, 45, 123, 456, 789);
+const result2 = datetime2.toLocaleString("en", options);
+assert.sameValue(result2, "8/4/2021, 23:30:45");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-always.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-always.js
new file mode 100644
index 0000000000..f927fd79f1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-always.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tostring
+description: Show calendar when calendarName is "always"
+features: [Temporal]
+---*/
+
+const dt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 0, 0, 0, 0, "gregory");
+
+assert.sameValue(
+ dt.toString({ calendarName: "always" }),
+ "1976-11-18T15:23:00[u-ca=gregory]",
+ "shows non-ISO calendar if calendarName = always"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-auto.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-auto.js
new file mode 100644
index 0000000000..e4b7ac4754
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-auto.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tostring
+description: Possibly display calendar when calendarName is "auto"
+features: [Temporal]
+---*/
+
+const dt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 0, 0, 0, 0, "gregory");
+const expected = "1976-11-18T15:23:00[u-ca=gregory]";
+
+assert.sameValue(dt.toString(), expected, "shows non-ISO calendar by default (no arguments)");
+assert.sameValue(dt.toString({ calendarName: "auto" }), expected, "shows only non-ISO calendar if calendarName = auto");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-never.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-never.js
new file mode 100644
index 0000000000..0477747774
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/calendarname-never.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tostring
+description: Do not show calendar (even non-ISO calendars) if calendarName = "never"
+features: [Temporal]
+---*/
+
+const dt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23);
+
+assert.sameValue(
+ dt.withCalendar("gregory").toString({ calendarName: "never" }),
+ "1976-11-18T15:23:00",
+ "omits non-ISO calendar if calendarName = never"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-string-datetime.js
new file mode 100644
index 0000000000..0a1fe0b201
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.tozoneddatetime
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2);
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toZonedDateTime(timeZone);
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..c42b700f6f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 15, 0, 0, 0, 0, 0, "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/until/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-noniso.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-noniso.js
new file mode 100644
index 0000000000..09f952c922
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-noniso.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: PlainDate calendar is preserved with ISO PDT
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const cal = {
+ id: 'thisisnotiso',
+ era() { return "the era"; },
+ eraYear() { return 1909; },
+ toString() { return "this is a string"; },
+ year() { return 2008; },
+ month() { return 9; },
+ monthCode() { return "M09"; },
+ day() { return 6; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const pdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 0, 0, 0);
+assert.sameValue(pdt.calendarId, "iso8601", "PlainDateTime with ISO calendar");
+const pd = new Temporal.PlainDate(2010, 11, 12, cal);
+const shifted = pdt.withPlainDate(pd);
+
+TemporalHelpers.assertPlainDateTime(
+ shifted,
+ 2008, 9, "M09", 6, 3, 24, 30, 0, 0, 0,
+ "calendar is changed if receiver has ISO calendar (1)",
+ "the era",
+ 1909
+);
+
+assert.sameValue(
+ shifted.getCalendar(),
+ cal,
+ "calendar is changed if receiver has ISO calendar (2)"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-id.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-id.js
new file mode 100644
index 0000000000..3d8d0f9dcb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-id.js
@@ -0,0 +1,80 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: PlainDate calendar is preserved when both calendars have the same id
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const cal1 = {
+ id: "this is a string",
+ toString() { return "this is a another string"; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const cal2 = {
+ id: "this is a string",
+ era() { return "the era"; },
+ eraYear() { return 1909; },
+ toString() { return "thisisnotiso"; },
+ year() { return 2008; },
+ month() { return 9; },
+ monthCode() { return "M09"; },
+ day() { return 6; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const pdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 0, 0, 0, cal1);
+const pd = new Temporal.PlainDate(2010, 11, 12, cal2);
+const shifted = pdt.withPlainDate(pd);
+
+TemporalHelpers.assertPlainDateTime(
+ shifted,
+ 2008, 9, "M09", 6, 3, 24, 30, 0, 0, 0,
+ "calendar is changed with same id (1)",
+ "the era",
+ 1909
+);
+
+assert.sameValue(
+ shifted.getCalendar(),
+ cal2,
+ "calendar is changed with same id (2)"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-object.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-object.js
new file mode 100644
index 0000000000..bdf08a0f7a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar-same-object.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: PlainDate calendar is preserved when both calendars are the same object
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+let calls = 0;
+const cal = {
+ get id() {
+ ++calls;
+ return "thisisnotiso";
+ },
+ era() { return "the era"; },
+ eraYear() { return 1909; },
+ toString() {
+ ++calls;
+ return "this is a string";
+ },
+ year() { return 2008; },
+ month() { return 9; },
+ monthCode() { return "M09"; },
+ day() { return 6; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const pdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 0, 0, 0, cal);
+const pd = new Temporal.PlainDate(2010, 11, 12, cal);
+const shifted = pdt.withPlainDate(pd);
+
+TemporalHelpers.assertPlainDateTime(
+ shifted,
+ 2008, 9, "M09", 6, 3, 24, 30, 0, 0, 0,
+ "calendar is unchanged with same calendars (1)",
+ "the era",
+ 1909
+);
+
+assert.sameValue(
+ shifted.getCalendar(),
+ cal,
+ "calendar is unchanged with same calendars (2)"
+);
+assert.sameValue(calls, 0, "should not have called cal.toString() or accessed cal.id");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar.js
new file mode 100644
index 0000000000..3def15ddb9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-plaindate-calendar.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: Original PDT calendar is preserved with ISO PlainDate
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const cal = {
+ id: 'thisisnotiso',
+ era() { return "the era"; },
+ eraYear() { return 1909; },
+ toString() { return "this is a string"; },
+ year() { return 2008; },
+ month() { return 9; },
+ monthCode() { return "M09"; },
+ day() { return 6; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const pdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 0, 0, 0, cal);
+const pd = new Temporal.PlainDate(2010, 11, 12);
+assert.sameValue(pd.calendarId, "iso8601", "PlainDate with ISO calendar");
+const shifted = pdt.withPlainDate(pd);
+
+TemporalHelpers.assertPlainDateTime(
+ shifted,
+ 2008, 9, "M09", 6, 3, 24, 30, 0, 0, 0,
+ "calendar is unchanged if input has ISO calendar (1)",
+ "the era",
+ 1909
+);
+
+assert.sameValue(
+ shifted.getCalendar(),
+ cal,
+ "calendar is unchanged if input has ISO calendar (2)"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar.js
new file mode 100644
index 0000000000..b81895586a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.withplaindate
+description: non-ISO calendars are handled correctly
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const isopdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 123, 456, 789);
+const gregorypdt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 123, 456, 789, "gregory");
+
+const result1 = isopdt.withPlainDate("2020-11-13[u-ca=gregory]");
+TemporalHelpers.assertPlainDateTime(result1, 2020, 11, "M11", 13, 3, 24, 30, 123, 456, 789,
+ "result1", "ce", 2020);
+assert.sameValue(result1.calendarId, "gregory", "non-ISO calendar in argument overrides ISO calendar in receiver");
+
+const result2 = gregorypdt.withPlainDate("2020-11-13[u-ca=iso8601]");
+TemporalHelpers.assertPlainDateTime(result2, 2020, 11, "M11", 13, 3, 24, 30, 123, 456, 789,
+ "result2", "ce", 2020);
+assert.sameValue(result2.calendarId, "gregory", "non-ISO calendar in receiver overrides ISO calendar in argument");
+
+assert.throws(RangeError, () => gregorypdt.withPlainDate("2020-11-13[u-ca=japanese]"),
+ "throws if both `this` and `other` have a non-ISO calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-iso-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-iso-calendar.js
new file mode 100644
index 0000000000..643327b592
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-iso-calendar.js
@@ -0,0 +1,55 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+description: Original PDT calendar is preserved with ISO string
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+const cal = {
+ id: "thisisnotiso",
+ era() { return "the era"; },
+ eraYear() { return 1909; },
+ toString() { return "this is a string"; },
+ year() { return 2008; },
+ month() { return 9; },
+ monthCode() { return "M09"; },
+ day() { return 6; },
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ inLeapYear() {},
+ mergeFields() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+};
+const dt = new Temporal.PlainDateTime(1995, 12, 7, 3, 24, 30, 0, 0, 0, cal);
+const shifted = dt.withPlainDate("2010-11-12");
+
+TemporalHelpers.assertPlainDateTime(
+ shifted,
+ 2008, 9, "M09", 6, 3, 24, 30, 0, 0, 0,
+ "calendar is unchanged if input has ISO calendar (1)",
+ "the era",
+ 1909
+);
+
+assert.sameValue(
+ shifted.getCalendar(),
+ cal,
+ "calendar is unchanged if input has ISO calendar (2)"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/browser.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..9c2991eab5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaindatetime.prototype.withplaindate
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainDateTime(2000, 5, 2, 15, 0, 0, 0, 0, 0, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/prototype/withPlainDate/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-missing-properties.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-missing-properties.js
new file mode 100644
index 0000000000..1c4765e9db
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-missing-properties.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.from
+description: >
+ Basic tests for PlainMonthDay.from(object) with missing properties for non-ISO
+ calendars
+features: [Temporal]
+---*/
+
+assert.throws(TypeError, () => Temporal.PlainMonthDay.from({ month: 11, day: 18, calendar: "gregory" }), "month, day with non-iso8601 calendar");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-object.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-object.js
new file mode 100644
index 0000000000..547f543246
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/fields-object.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.from
+description: Basic tests for PlainMonthDay.from(object) with non-ISO calendar
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const okTests = [
+ [{ monthCode: "M08", day: 1, calendar: "gregory" }, "gregory", "monthCode and non-ISO Gregorian string calendar"],
+ [{ monthCode: "M08", day: 1, calendar: "hebrew" }, "hebrew", "monthCode and non-ISO non-Gregorian string calendar"],
+ [{ monthCode: "M08", day: 1, calendar: Temporal.Calendar.from("gregory") }, "gregory", "monthCode and non-ISO Gregorian object calendar"],
+ [{ monthCode: "M08", day: 1, calendar: Temporal.Calendar.from("hebrew") }, "hebrew", "monthCode and non-ISO non-Gregorian object calendar"],
+];
+
+for (const [argument, expectedCalendar, description] of okTests) {
+ const plainMonthDay = Temporal.PlainMonthDay.from(argument);
+ TemporalHelpers.assertPlainMonthDay(plainMonthDay, "M08", 1, description);
+ assert.sameValue(plainMonthDay.calendarId, expectedCalendar, `resulting calendar is ${expectedCalendar}`);
+}
+
+const notOkTests = [
+ [{ month: 8, day: 1, calendar: "gregory" }, "month and non-ISO string calendar"],
+ [{ month: 8, day: 1, calendar: "hebrew" }, "month and non-ISO non-Gregorian string calendar"],
+ [{ month: 8, day: 1, calendar: Temporal.Calendar.from("gregory") }, "month and non-ISO Gregorian object calendar"],
+ [{ month: 8, day: 1, calendar: Temporal.Calendar.from("hebrew") }, "month and non-ISO non-Gregorian object calendar"],
+];
+
+for (const [argument, description] of notOkTests) {
+ assert.throws(TypeError, () => Temporal.PlainMonthDay.from(argument), description);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/reference-date-noniso-calendar.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/reference-date-noniso-calendar.js
new file mode 100644
index 0000000000..bf6eed6261
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/reference-date-noniso-calendar.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.from
+description: Verify that the result of ToTemporalMonthDay preserves year information for Non-ISO calendars.
+info: |
+ sec-temporal.plainmonthday.from step 3:
+ 3. Return ? ToTemporalMonthDay(_item_, _options_).
+ sec-temporal-totemporalmonthday step 11.:
+ 11. Set result to ? CreateTemporalMonthDay(_result_.[[Month]], _result_.[[Day]], _calendar_, _result_.[[Year]]).
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const pmd = Temporal.PlainMonthDay.from("2023-01-01[u-ca=hebrew]")
+TemporalHelpers.assertPlainMonthDay(pmd, "M04", 8); // 2023-01-01 corresponds to 8 Tevet in Hebrew Calendar.
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/from/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..3494b1644d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainmonthday.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainMonthDay(5, 2, "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/equals/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js
new file mode 100644
index 0000000000..5cad007603
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.tolocalestring
+description: Calendar must match the locale calendar if not "iso8601"
+features: [Temporal, Intl-enumeration]
+---*/
+
+const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
+assert.notSameValue(localeCalendar, "iso8601", "no locale has the ISO calendar");
+
+const sameCalendarInstance = Temporal.PlainMonthDay.from({ monthCode: "M01", day: 1, calendar: localeCalendar });
+const result = sameCalendarInstance.toLocaleString();
+assert.sameValue(typeof result, "string", "toLocaleString() succeeds when instance has the same calendar as locale");
+
+// Pick a different calendar that is not ISO and not the locale's calendar
+const calendars = new Set(Intl.supportedValuesOf("calendar"));
+calendars.delete("iso8601");
+calendars.delete(localeCalendar);
+const differentCalendar = calendars.values().next().value;
+
+const differentCalendarInstance = Temporal.PlainMonthDay.from({ monthCode: "M01", day: 1, calendar: differentCalendar });
+assert.throws(RangeError, () => differentCalendarInstance.toLocaleString(), "calendar mismatch");
+
+const isoInstance = Temporal.PlainMonthDay.from({ monthCode: "M01", day: 1, calendar: "iso8601" });
+assert.throws(RangeError, () => isoInstance.toLocaleString(), "calendar mismatch even when instance has the ISO calendar")
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..96f40e0394
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [Temporal]
+---*/
+
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const { calendar } = defaultFormatter.resolvedOptions();
+const monthday = new Temporal.PlainMonthDay(5, 2, calendar);
+const expected = defaultFormatter.format(monthday);
+
+const actualExplicit = monthday.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = monthday.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..e59afa1d33
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [Temporal]
+---*/
+
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const { calendar } = defaultFormatter.resolvedOptions();
+const monthday = new Temporal.PlainMonthDay(5, 2, calendar);
+const expected = defaultFormatter.format(monthday);
+
+const actualExplicit = monthday.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = monthday.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/resolved-time-zone.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/resolved-time-zone.js
new file mode 100644
index 0000000000..bc66136c4f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/resolved-time-zone.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.tolocalestring
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+const monthDay = new Temporal.PlainMonthDay(8, 4, "gregory");
+const result = monthDay.toLocaleString("en", { timeZone: "Pacific/Apia" });
+assert.sameValue(result, "8/4");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..3adad5b50a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/infinity-throws-rangeerror.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2020 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws a RangeError if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainmonthday.prototype.toplaindate
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainMonthDay(5, 2, "gregory");
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.toPlainDate({ era: "ad", eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.toPlainDate({ era: "ad", eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/toPlainDate/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/browser.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/fields-missing-properties.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/fields-missing-properties.js
new file mode 100644
index 0000000000..209ae9cd4f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/fields-missing-properties.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainmonthday.prototype.with
+description: >
+ Basic tests for with(object) with missing properties for non-ISO calendars
+features: [Temporal]
+---*/
+
+const calendarMonthDay = Temporal.PlainMonthDay.from({ year: 2021, month: 1, day: 15, calendar: "gregory" });
+assert.throws(TypeError, () => calendarMonthDay.with({ month: 12 }), "nonIso8601MonthDay.with({month})");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/prototype/with/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainMonthDay/shell.js b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainMonthDay/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..43e8913bd5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [Temporal]
+---*/
+
+const time = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const expected = defaultFormatter.format(time);
+
+const actualExplicit = time.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = time.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..31507d4c82
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.plaintime.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "fractionalSecondDigits", 3 ],
+];
+const time = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+
+assert.sameValue(typeof time.toLocaleString("en", { timeStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ time.toLocaleString("en", { [option]: value, timeStyle: "short" });
+ }, `time.toLocaleString("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..adb1a6f614
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [Temporal]
+---*/
+
+const time = new Temporal.PlainTime(12, 34, 56, 987, 654, 321);
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const expected = defaultFormatter.format(time);
+
+const actualExplicit = time.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default options are determined by Intl.DateTimeFormat");
+
+const actualImplicit = time.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default options are determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/resolved-time-zone.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/resolved-time-zone.js
new file mode 100644
index 0000000000..5844e0210f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/resolved-time-zone.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tolocalestring
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+// Using 24-hour clock to avoid format differences between Node 19 (which puts
+// "\u{202f}" before AM/PM) and previous versions that use regular spaces.
+const options = {
+ timeZone: "Pacific/Apia",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ hourCycle: "h23"
+};
+
+const time1 = new Temporal.PlainTime(0, 30, 45, 123, 456, 789);
+const result1 = time1.toLocaleString("en", options);
+assert.sameValue(result1, "00:30:45");
+
+const time2 = new Temporal.PlainTime(23, 30, 45, 123, 456, 789);
+const result2 = time2.toLocaleString("en", options);
+assert.sameValue(result2, "23:30:45");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..359d5f058a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaintime.prototype.toplaindatetime
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainTime(15);
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.toPlainDateTime({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.toPlainDateTime({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toPlainDateTime/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/plaindate-infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/plaindate-infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..c7423da29c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/plaindate-infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainTime(15);
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone: "UTC", plainDate: { ...base, eraYear: inf } }), `eraYear property cannot be ${inf} in plainDate`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.toZonedDateTime({ timeZone: "UTC", plainDate: { ...base, eraYear: obj } }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/timezone-string-datetime.js
new file mode 100644
index 0000000000..51293aa08c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/prototype/toZonedDateTime/timezone-string-datetime.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plaintime.prototype.tozoneddatetime
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainTime();
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = instance.toZonedDateTime({ plainDate: new Temporal.PlainDate(2000, 5, 2), timeZone });
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = instance.toZonedDateTime({ plainDate: new Temporal.PlainDate(2000, 5, 2), timeZone });
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = instance.toZonedDateTime({ plainDate: new Temporal.PlainDate(2000, 5, 2), timeZone });
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainTime/shell.js b/js/src/tests/test262/intl402/Temporal/PlainTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..4ee79a26e6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/infinity-throws-rangeerror.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.plainyearmonth.compare
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const other = new Temporal.PlainYearMonth(2000, 5, "gregory");
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.compare({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.compare(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.compare({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.compare(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/compare/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/argument-object.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/argument-object.js
new file mode 100644
index 0000000000..a8bf0eb747
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/argument-object.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.from
+description: An object argument
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const monthDayItem = { calendar: "gregory", era: "ce", eraYear: 2019, month: 11, get day() { throw new Test262Error("should not read the day property") } };
+TemporalHelpers.assertPlainYearMonth(Temporal.PlainYearMonth.from(monthDayItem),
+ 2019, 11, "M11", "month with day", "ce", 2019);
+
+const monthCodeDayItem = { calendar: "gregory", era: "ce", eraYear: 2019, monthCode: "M11", get day() { throw new Test262Error("should not read the day property") } };
+TemporalHelpers.assertPlainYearMonth(Temporal.PlainYearMonth.from(monthCodeDayItem),
+ 2019, 11, "M11", "monthCode with day", "ce", 2019);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..54792dbce0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/infinity-throws-rangeerror.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainyearmonth.from
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.from({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.PlainYearMonth.from({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/from/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..00e3922f1d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainyearmonth.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2000, 5, "gregory");
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/equals/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/branding.js
new file mode 100644
index 0000000000..250e5eaee9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Object.getOwnPropertyDescriptor(Temporal.PlainYearMonth.prototype, "era").get;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.PlainYearMonth), "Temporal.PlainYearMonth");
+assert.throws(TypeError, () => era.call(Temporal.PlainYearMonth.prototype), "Temporal.PlainYearMonth.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..90188ed459
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.era
+description: The "era" property of Temporal.PlainYearMonth.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainYearMonth.prototype, "era");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/validate-calendar-value.js
new file mode 100644
index 0000000000..f49c5548de
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/era/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.era
+description: Validate result returned from calendar era() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, TypeError],
+ [-Infinity, TypeError],
+ [NaN, TypeError],
+ [-7, TypeError],
+ [-0.1, TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, TypeError],
+ [{valueOf() { return "7"; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainYearMonth(1981, 12, calendar);
+ assert.throws(error, () => instance.era, `${typeof result} ${String(result)} not converted to string`);
+});
+
+const preservedResults = [
+ undefined,
+ "string",
+ "7",
+ "7.5",
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainYearMonth(1981, 12, calendar);
+ assert.sameValue(instance.era, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..e847e93a43
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Object.getOwnPropertyDescriptor(Temporal.PlainYearMonth.prototype, "eraYear").get;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainYearMonth), "Temporal.PlainYearMonth");
+assert.throws(TypeError, () => eraYear.call(Temporal.PlainYearMonth.prototype), "Temporal.PlainYearMonth.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..5ab24b9593
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.erayear
+description: The "eraYear" property of Temporal.PlainYearMonth.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.PlainYearMonth.prototype, "eraYear");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/validate-calendar-value.js
new file mode 100644
index 0000000000..296a9f3e15
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/eraYear/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.plainyearmonth.prototype.erayear
+description: Validate result returned from calendar eraYear() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, RangeError],
+ [-Infinity, RangeError],
+ [NaN, RangeError],
+ [-0.1, RangeError],
+ ["string", TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, RangeError],
+ ["7", TypeError],
+ ["7.5", TypeError],
+ [{valueOf() { return 7; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainYearMonth(1981, 12, calendar);
+ assert.throws(error, () => instance.eraYear, `${typeof result} ${String(result)} not converted to integer`);
+});
+
+const preservedResults = [
+ undefined,
+ -7,
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.PlainYearMonth(1981, 12, calendar);
+ assert.sameValue(instance.eraYear, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..f4b81344f4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainyearmonth.prototype.since
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2000, 5, "gregory");
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/since/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js
new file mode 100644
index 0000000000..7c98e589a0
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.tolocalestring
+description: Calendar must match the locale calendar
+features: [Temporal, Intl-enumeration]
+---*/
+
+const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
+assert.notSameValue(localeCalendar, "iso8601", "no locale has the ISO calendar");
+
+const sameCalendarInstance = new Temporal.PlainDate(2000, 1, 1, localeCalendar).toPlainYearMonth();
+const result = sameCalendarInstance.toLocaleString();
+assert.sameValue(typeof result, "string", "toLocaleString() succeeds when instance has the same calendar as locale");
+
+// Pick a different calendar that is not ISO and not the locale's calendar
+const calendars = new Set(Intl.supportedValuesOf("calendar"));
+calendars.delete("iso8601");
+calendars.delete(localeCalendar);
+const differentCalendar = calendars.values().next().value;
+
+const differentCalendarInstance = new Temporal.PlainDate(2000, 1, 1, differentCalendar).toPlainYearMonth();
+assert.throws(RangeError, () => differentCalendarInstance.toLocaleString(), "calendar mismatch");
+
+const isoInstance = new Temporal.PlainDate(2000, 1, 1, "iso8601").toPlainYearMonth();
+assert.throws(RangeError, () => isoInstance.toLocaleString(), "calendar mismatch even when instance has the ISO calendar")
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..be3ff739ca
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [Temporal]
+---*/
+
+const defaultFormatter = new Intl.DateTimeFormat([], Object.create(null));
+const { calendar } = defaultFormatter.resolvedOptions();
+const yearmonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+const expected = defaultFormatter.format(yearmonth);
+
+const actualExplicit = yearmonth.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = yearmonth.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..fa036ddc6f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [Temporal]
+---*/
+
+const defaultFormatter = new Intl.DateTimeFormat('en', Object.create(null));
+const { calendar } = defaultFormatter.resolvedOptions();
+const yearmonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+const expected = defaultFormatter.format(yearmonth);
+
+const actualExplicit = yearmonth.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = yearmonth.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/resolved-time-zone.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/resolved-time-zone.js
new file mode 100644
index 0000000000..1dc10ccf38
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/resolved-time-zone.js
@@ -0,0 +1,16 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.plainyearmonth.prototype.tolocalestring
+description: A time zone in resolvedOptions with a large offset still produces the correct string
+locale: [en]
+features: [Temporal]
+---*/
+
+const month = new Temporal.PlainYearMonth(2021, 8, "gregory");
+const result = month.toLocaleString("en", { timeZone: "Pacific/Apia" });
+assert.sameValue(result, "8/2021");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/browser.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..5e909a3f0c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.plainyearmonth.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.PlainYearMonth(2000, 5, "gregory");
+const base = { era: "ad", month: 5, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/prototype/until/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/PlainYearMonth/shell.js b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/PlainYearMonth/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/basic.js b/js/src/tests/test262/intl402/Temporal/TimeZone/basic.js
new file mode 100644
index 0000000000..2344d05187
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/basic.js
@@ -0,0 +1,32 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: Basic tests for the Temporal.TimeZone constructor.
+features: [Temporal]
+---*/
+
+const valid = [
+ ["Europe/Vienna"],
+ ["America/New_York"],
+ ["Africa/CAIRO", "Africa/Cairo"],
+ ["africa/cairo", "Africa/Cairo"],
+ ["Asia/Ulaanbaatar"],
+ ["Asia/Ulan_Bator"],
+ ["UTC"],
+ ["GMT"]
+];
+for (const [zone, id = zone] of valid) {
+ const result = new Temporal.TimeZone(zone);
+ assert.sameValue(typeof result, "object", `object should be created for ${zone}`);
+ assert.sameValue(result.id, id, `id for ${zone} should be ${id}`);
+}
+
+const invalid = ["+00:01.1", "-01.1"];
+for (const zone of invalid) {
+ assert.throws(RangeError, () => new Temporal.TimeZone(zone), `should throw for ${zone}`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/etc-timezone.js b/js/src/tests/test262/intl402/Temporal/TimeZone/etc-timezone.js
new file mode 100644
index 0000000000..7703f6b58f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/etc-timezone.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: Some Etc/GMT{+/-}{0}N timezones are valid, but not all
+features: [Temporal]
+---*/
+
+// "Etc/GMT-0" through "Etc/GMT-14" are OK
+
+[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14].forEach((n) => {
+ let tz = "Etc/GMT-" + n;
+ let instance = new Temporal.TimeZone(tz);
+ assert.sameValue(
+ instance.toString(),
+ tz,
+ tz + " is a valid timezone"
+ );
+});
+
+let gmtMinus24TZ = "Etc/GMT-24";
+assert.throws(
+ RangeError,
+ () => { new Temporal.TimeZone(gmtMinus24TZ); },
+ gmtMinus24TZ + " is an invalid timezone"
+);
+
+// "Etc/GMT-0N" is not OK (1 ≤ N ≤ 9)
+[1,2,3,4,5,6,7,8,9].forEach((n) => {
+ let tz = "Etc/GMT-0" + n;
+ assert.throws(
+ RangeError,
+ () => { new Temporal.TimeZone(tz); },
+ tz + " is an invalid timezone"
+ );
+});
+
+// "Etc/GMT+0N" is not OK (0 ≤ N ≤ 9)
+[0,1,2,3,4,5,6,7,8,9].forEach((n) => {
+ let tz = "Etc/GMT+0" + n;
+ assert.throws(
+ RangeError,
+ () => { new Temporal.TimeZone(tz); },
+ tz + " is an invalid timezone"
+ );
+});
+
+// Etc/GMT+0" through "Etc/GMT+12" are OK
+
+[0,1,2,3,4,5,6,7,8,9,10,11,12].forEach((n) => {
+ let tz = "Etc/GMT+" + n;
+ let instance = new Temporal.TimeZone(tz);
+ assert.sameValue(
+ instance.toString(),
+ tz,
+ tz + " is a valid timezone"
+ );
+});
+
+let gmtPlus24TZ = "Etc/GMT+24";
+assert.throws(
+ RangeError,
+ () => { new Temporal.TimeZone(gmtPlus24TZ); },
+ gmtPlus24TZ + " is an invalid timezone"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-object.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-object.js
new file mode 100644
index 0000000000..64c3264f09
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-object.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2020 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: An object is returned unchanged
+features: [Temporal]
+---*/
+
+class CustomTimeZone extends Temporal.TimeZone {}
+
+const objects = [
+ new Temporal.TimeZone("Europe/Madrid"),
+ new CustomTimeZone("Africa/Accra"),
+];
+
+const thisValues = [
+ Temporal.TimeZone,
+ CustomTimeZone,
+ {},
+ null,
+ undefined,
+ 7,
+];
+
+for (const thisValue of thisValues) {
+ for (const object of objects) {
+ const result = Temporal.TimeZone.from.call(thisValue, object);
+ assert.sameValue(result, object);
+ }
+
+ const zdt = new Temporal.ZonedDateTime(0n, "Africa/Cairo");
+ const fromZdt = Temporal.TimeZone.from.call(thisValue, zdt);
+ assert.notSameValue(fromZdt, zdt.getTimeZone(), "from() creates a new object for a string slot value");
+ assert.sameValue(fromZdt.id, "Africa/Cairo");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-valid.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-valid.js
new file mode 100644
index 0000000000..340be95d3b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/argument-valid.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2020 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Built-in time zones are parsed correctly out of valid strings
+features: [Temporal]
+---*/
+
+const valids = [
+ ["Africa/Bissau"],
+ ["America/Belem"],
+ ["Europe/Vienna"],
+ ["America/New_York"],
+ ["Africa/CAIRO", "Africa/Cairo"],
+ ["Asia/Ulan_Bator"],
+ ["GMT"],
+ ["etc/gmt", "Etc/GMT"],
+ ["1994-11-05T08:15:30-05:00[America/New_York]", "America/New_York"],
+ ["1994-11-05T08:15:30-05[America/New_York]", "America/New_York"],
+ ["1994-11-05T08:15:30\u221205:00[America/New_York]", "America/New_York"],
+ ["1994-11-05T08:15:30\u221205[America/New_York]", "America/New_York"],
+];
+
+for (const [valid, canonical = valid] of valids) {
+ const result = Temporal.TimeZone.from(valid);
+ assert.sameValue(Object.getPrototypeOf(result), Temporal.TimeZone.prototype);
+ assert.sameValue(result.id, canonical);
+ assert.sameValue(result.toString(), canonical);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/etc-timezone.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/etc-timezone.js
new file mode 100644
index 0000000000..b29088007e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/etc-timezone.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Some Etc/GMT{+/-}{0}N timezones are valid, but not all
+features: [Temporal]
+---*/
+
+// "Etc/GMT-0" through "Etc/GMT-14" are OK
+
+[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14].forEach((n) => {
+ const tz = "Etc/GMT-" + n;
+ const instance = Temporal.TimeZone.from(tz);
+ assert.sameValue(
+ instance.toString(),
+ tz,
+ tz + " is a valid timezone"
+ );
+});
+
+const gmtMinus24TZ = "Etc/GMT-24";
+assert.throws(
+ RangeError,
+ () => Temporal.TimeZone.from(gmtMinus24TZ),
+ gmtMinus24TZ + " is an invalid timezone"
+);
+
+// "Etc/GMT-0N" is not OK (1 ≤ N ≤ 9)
+[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((n) => {
+ const tz = "Etc/GMT-0" + n;
+ assert.throws(
+ RangeError,
+ () => Temporal.TimeZone.from(tz),
+ tz + " is an invalid timezone"
+ );
+});
+
+// "Etc/GMT+0N" is not OK (0 ≤ N ≤ 9)
+[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((n) => {
+ const tz = "Etc/GMT+0" + n;
+ assert.throws(
+ RangeError,
+ () => Temporal.TimeZone.from(tz),
+ tz + " is an invalid timezone"
+ );
+});
+
+// "Etc/GMT+0" through "Etc/GMT+12" are OK
+
+[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].forEach((n) => {
+ const tz = "Etc/GMT+" + n;
+ const instance = Temporal.TimeZone.from(tz);
+ assert.sameValue(
+ instance.toString(),
+ tz,
+ tz + " is a valid timezone"
+ );
+});
+
+const gmtPlus24TZ = "Etc/GMT+24";
+assert.throws(
+ RangeError,
+ () => Temporal.TimeZone.from(gmtPlus24TZ),
+ gmtPlus24TZ + " is an invalid timezone"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/iana-legacy-names.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/iana-legacy-names.js
new file mode 100644
index 0000000000..d04aa99656
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/iana-legacy-names.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: IANA legacy names must be supported
+features: [Temporal]
+---*/
+
+const legacyNames = [
+ "Etc/GMT0",
+ "GMT0",
+ "GMT-0",
+ "GMT+0",
+ "EST5EDT",
+ "CST6CDT",
+ "MST7MDT",
+ "PST8PDT"
+];
+
+legacyNames.forEach((arg) => {
+ const tz = Temporal.TimeZone.from(arg);
+ assert.sameValue(tz.toString(), arg, `"${arg}" does not match "${tz.toString()}" time zone identifier`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-case-insensitive.js
new file mode 100644
index 0000000000..52a86b6497
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-case-insensitive.js
@@ -0,0 +1,628 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Time zone identifiers are case-normalized
+features: [Temporal]
+---*/
+
+const timeZoneIdentifiers = [
+ // IANA TZDB Zone names
+ 'Africa/Abidjan',
+ 'Africa/Algiers',
+ 'Africa/Bissau',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/El_Aaiun',
+ 'Africa/Johannesburg',
+ 'Africa/Juba',
+ 'Africa/Khartoum',
+ 'Africa/Lagos',
+ 'Africa/Maputo',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Sao_Tome',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Asuncion',
+ 'America/Bahia',
+ 'America/Bahia_Banderas',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Cayenne',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ // 'America/Ciudad_Juarez' // uncomment after Node supports this ID added in TZDB 2022g
+ 'America/Costa_Rica',
+ 'America/Cuiaba',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Fort_Nelson',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Metlakatla',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/New_York',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Beulah',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Nuuk',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port-au-Prince',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Punta_Arenas',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/Sitka',
+ 'America/St_Johns',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Vancouver',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/Macquarie',
+ 'Antarctica/Mawson',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/Troll',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Atyrau',
+ 'Asia/Baghdad',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Barnaul',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Chita',
+ 'Asia/Choibalsan',
+ 'Asia/Colombo',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Famagusta',
+ 'Asia/Gaza',
+ 'Asia/Hebron',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kathmandu',
+ 'Asia/Khandyga',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuching',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qostanay',
+ 'Asia/Qyzylorda',
+ 'Asia/Riyadh',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Srednekolymsk',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Tomsk',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Urumqi',
+ 'Asia/Ust-Nera',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yangon',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faroe',
+ 'Atlantic/Madeira',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/Stanley',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/Perth',
+ 'Australia/Sydney',
+ 'CET',
+ 'CST6CDT',
+ 'EET',
+ 'EST',
+ 'EST5EDT',
+ 'Etc/GMT',
+ 'Etc/GMT+1',
+ 'Etc/GMT+10',
+ 'Etc/GMT+11',
+ 'Etc/GMT+12',
+ 'Etc/GMT+2',
+ 'Etc/GMT+3',
+ 'Etc/GMT+4',
+ 'Etc/GMT+5',
+ 'Etc/GMT+6',
+ 'Etc/GMT+7',
+ 'Etc/GMT+8',
+ 'Etc/GMT+9',
+ 'Etc/GMT-1',
+ 'Etc/GMT-10',
+ 'Etc/GMT-11',
+ 'Etc/GMT-12',
+ 'Etc/GMT-13',
+ 'Etc/GMT-14',
+ 'Etc/GMT-2',
+ 'Etc/GMT-3',
+ 'Etc/GMT-4',
+ 'Etc/GMT-5',
+ 'Etc/GMT-6',
+ 'Etc/GMT-7',
+ 'Etc/GMT-8',
+ 'Etc/GMT-9',
+ 'Etc/UTC',
+ 'Europe/Andorra',
+ 'Europe/Astrakhan',
+ 'Europe/Athens',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Chisinau',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Helsinki',
+ 'Europe/Istanbul',
+ 'Europe/Kaliningrad',
+ 'Europe/Kirov',
+ 'Europe/Kyiv',
+ 'Europe/Lisbon',
+ 'Europe/London',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Minsk',
+ 'Europe/Moscow',
+ 'Europe/Paris',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/Saratov',
+ 'Europe/Simferopol',
+ 'Europe/Sofia',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Ulyanovsk',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zurich',
+ 'HST',
+ 'Indian/Chagos',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'MET',
+ 'MST',
+ 'MST7MDT',
+ 'PST8PDT',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Bougainville',
+ 'Pacific/Chatham',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Kanton',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Marquesas',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+
+ // IANA TZDB Link names
+ 'WET',
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Kampala',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Timbuktu',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Argentina/ComodRivadavia',
+ 'America/Aruba',
+ 'America/Atikokan',
+ 'America/Atka',
+ 'America/Blanc-Sablon',
+ 'America/Buenos_Aires',
+ 'America/Catamarca',
+ 'America/Cayman',
+ 'America/Coral_Harbour',
+ 'America/Cordoba',
+ 'America/Creston',
+ 'America/Curacao',
+ 'America/Dominica',
+ 'America/Ensenada',
+ 'America/Fort_Wayne',
+ 'America/Godthab',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Indianapolis',
+ 'America/Jujuy',
+ 'America/Knox_IN',
+ 'America/Kralendijk',
+ 'America/Louisville',
+ 'America/Lower_Princes',
+ 'America/Marigot',
+ 'America/Mendoza',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/Nipigon',
+ 'America/Pangnirtung',
+ 'America/Port_of_Spain',
+ 'America/Porto_Acre',
+ 'America/Rainy_River',
+ 'America/Rosario',
+ 'America/Santa_Isabel',
+ 'America/Shiprock',
+ 'America/St_Barthelemy',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Thunder_Bay',
+ 'America/Tortola',
+ 'America/Virgin',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/McMurdo',
+ 'Antarctica/South_Pole',
+ 'Antarctica/Syowa',
+ 'Antarctica/Vostok',
+ 'Arctic/Longyearbyen',
+ 'Asia/Aden',
+ 'Asia/Ashkhabad',
+ 'Asia/Bahrain',
+ 'Asia/Brunei',
+ 'Asia/Calcutta',
+ 'Asia/Chongqing',
+ 'Asia/Chungking',
+ 'Asia/Dacca',
+ 'Asia/Harbin',
+ 'Asia/Istanbul',
+ 'Asia/Kashgar',
+ 'Asia/Katmandu',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuwait',
+ 'Asia/Macao',
+ 'Asia/Muscat',
+ 'Asia/Phnom_Penh',
+ 'Asia/Rangoon',
+ 'Asia/Saigon',
+ 'Asia/Tel_Aviv',
+ 'Asia/Thimbu',
+ 'Asia/Ujung_Pandang',
+ 'Asia/Ulan_Bator',
+ 'Asia/Vientiane',
+ 'Atlantic/Faeroe',
+ 'Atlantic/Jan_Mayen',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/St_Helena',
+ 'Australia/ACT',
+ 'Australia/Canberra',
+ 'Australia/Currie',
+ 'Australia/LHI',
+ 'Australia/NSW',
+ 'Australia/North',
+ 'Australia/Queensland',
+ 'Australia/South',
+ 'Australia/Tasmania',
+ 'Australia/Victoria',
+ 'Australia/West',
+ 'Australia/Yancowinna',
+ 'Brazil/Acre',
+ 'Brazil/DeNoronha',
+ 'Brazil/East',
+ 'Brazil/West',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Canada/Saskatchewan',
+ 'Canada/Yukon',
+ 'Chile/Continental',
+ 'Chile/EasterIsland',
+ 'Cuba',
+ 'Egypt',
+ 'Eire',
+ 'Etc/GMT+0',
+ 'Etc/GMT-0',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'Etc/UCT',
+ 'Etc/Universal',
+ 'Etc/Zulu',
+ 'Europe/Amsterdam',
+ 'Europe/Belfast',
+ 'Europe/Bratislava',
+ 'Europe/Busingen',
+ 'Europe/Copenhagen',
+ 'Europe/Guernsey',
+ 'Europe/Isle_of_Man',
+ 'Europe/Jersey',
+ 'Europe/Kiev',
+ 'Europe/Ljubljana',
+ 'Europe/Luxembourg',
+ 'Europe/Mariehamn',
+ 'Europe/Monaco',
+ 'Europe/Nicosia',
+ 'Europe/Oslo',
+ 'Europe/Podgorica',
+ 'Europe/San_Marino',
+ 'Europe/Sarajevo',
+ 'Europe/Skopje',
+ 'Europe/Stockholm',
+ 'Europe/Tiraspol',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vatican',
+ 'Europe/Zagreb',
+ 'Europe/Zaporozhye',
+ 'GB',
+ 'GB-Eire',
+ 'GMT',
+ 'GMT+0',
+ 'GMT-0',
+ 'GMT0',
+ 'Greenwich',
+ 'Hongkong',
+ 'Iceland',
+ 'Indian/Antananarivo',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Iran',
+ 'Israel',
+ 'Jamaica',
+ 'Japan',
+ 'Kwajalein',
+ 'Libya',
+ 'Mexico/BajaNorte',
+ 'Mexico/BajaSur',
+ 'Mexico/General',
+ 'NZ',
+ 'NZ-CHAT',
+ 'Navajo',
+ 'PRC',
+ 'Pacific/Chuuk',
+ 'Pacific/Enderbury',
+ 'Pacific/Funafuti',
+ 'Pacific/Johnston',
+ 'Pacific/Majuro',
+ 'Pacific/Midway',
+ 'Pacific/Pohnpei',
+ 'Pacific/Ponape',
+ 'Pacific/Saipan',
+ 'Pacific/Samoa',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'Pacific/Yap',
+ 'Poland',
+ 'Portugal',
+ 'ROC',
+ 'ROK',
+ 'Singapore',
+ 'Turkey',
+ 'UCT',
+ 'US/Alaska',
+ 'US/Aleutian',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/East-Indiana',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Indiana-Starke',
+ 'US/Michigan',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'US/Pacific-New',
+ 'US/Samoa',
+ 'UTC',
+ 'Universal',
+ 'W-SU',
+ 'Zulu'
+];
+
+// We want to test all available named time zone identifiers (both primary and non-primary),
+// but no ECMAScript built-in API exposes that list. So we use a union of two sources:
+// 1. A hard-coded list of Zone and Link identifiers from the 2022g version of IANA TZDB.
+// 2. Canonical IDs exposed by Intl.supportedValuesOf('timeZone'), which ensures that IDs
+// added to TZDB later than 2022g will be tested. (New IDs are almost always added as primary.)
+const ids = [...new Set([...timeZoneIdentifiers, ...Intl.supportedValuesOf('timeZone')])];
+for (const id of ids) {
+ const lower = id.toLowerCase();
+ const upper = id.toUpperCase();
+ assert.sameValue(new Temporal.TimeZone(id).toString(), id, `Time zone created from string "${id}"`);
+ assert.sameValue(new Temporal.TimeZone(upper).toString(), id, `Time zone created from string "${upper}"`);
+ assert.sameValue(new Temporal.TimeZone(lower).toString(), id, `Time zone created from string "${lower}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-string-datetime.js
new file mode 100644
index 0000000000..c0b0a03f5c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/from/timezone-string-datetime.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.TimeZone.from(timeZone);
+assert.sameValue(result1.id, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = Temporal.TimeZone.from(timeZone);
+assert.sameValue(result2.id, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = Temporal.TimeZone.from(timeZone);
+assert.sameValue(result3.id, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/iana-legacy-names.js b/js/src/tests/test262/intl402/Temporal/TimeZone/iana-legacy-names.js
new file mode 100644
index 0000000000..150633f6ce
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/iana-legacy-names.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: IANA legacy names must be supported
+features: [Temporal]
+---*/
+
+const legacyNames = [
+ "Etc/GMT0",
+ "GMT0",
+ "GMT-0",
+ "GMT+0",
+ "EST5EDT",
+ "CST6CDT",
+ "MST7MDT",
+ "PST8PDT"
+];
+
+legacyNames.forEach((arg) => {
+ const tz = new Temporal.TimeZone(arg);
+ assert.sameValue(tz.toString(), arg, `"${arg}" does not match "${tz.toString()}" time zone identifier`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-africa.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-africa.js
new file mode 100644
index 0000000000..0e5ed598b1
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-africa.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Africa/Accra", // Link Africa/Abidjan Africa/Accra # Ghana
+ "Africa/Bamako", // Link Africa/Abidjan Africa/Bamako # Mali
+ "Africa/Banjul", // Link Africa/Abidjan Africa/Banjul # The Gambia
+ "Africa/Conakry", // Link Africa/Abidjan Africa/Conakry # Guinea
+ "Africa/Dakar", // Link Africa/Abidjan Africa/Dakar # Senegal
+ "Africa/Freetown", // Link Africa/Abidjan Africa/Freetown # Sierra Leone
+ "Africa/Lome", // Link Africa/Abidjan Africa/Lome # Togo
+ "Africa/Nouakchott", // Link Africa/Abidjan Africa/Nouakchott # Mauritania
+ "Africa/Ouagadougou", // Link Africa/Abidjan Africa/Ouagadougou # Burkina Faso
+ "Atlantic/St_Helena", // Link Africa/Abidjan Atlantic/St_Helena # St Helena
+ "Africa/Addis_Ababa", // Link Africa/Nairobi Africa/Addis_Ababa # Ethiopia
+ "Africa/Asmara", // Link Africa/Nairobi Africa/Asmara # Eritrea
+ "Africa/Dar_es_Salaam", // Link Africa/Nairobi Africa/Dar_es_Salaam # Tanzania
+ "Africa/Djibouti", // Link Africa/Nairobi Africa/Djibouti
+ "Africa/Kampala", // Link Africa/Nairobi Africa/Kampala # Uganda
+ "Africa/Mogadishu", // Link Africa/Nairobi Africa/Mogadishu # Somalia
+ "Indian/Antananarivo", // Link Africa/Nairobi Indian/Antananarivo # Madagascar
+ "Indian/Comoro", // Link Africa/Nairobi Indian/Comoro
+ "Indian/Mayotte", // Link Africa/Nairobi Indian/Mayotte
+ "Africa/Blantyre", // Link Africa/Maputo Africa/Blantyre # Malawi
+ "Africa/Bujumbura", // Link Africa/Maputo Africa/Bujumbura # Burundi
+ "Africa/Gaborone", // Link Africa/Maputo Africa/Gaborone # Botswana
+ "Africa/Harare", // Link Africa/Maputo Africa/Harare # Zimbabwe
+ "Africa/Kigali", // Link Africa/Maputo Africa/Kigali # Rwanda
+ "Africa/Lubumbashi", // Link Africa/Maputo Africa/Lubumbashi # E Dem. Rep. of Congo
+ "Africa/Lusaka", // Link Africa/Maputo Africa/Lusaka # Zambia
+ "Africa/Bangui", // Link Africa/Lagos Africa/Bangui # Central African Republic
+ "Africa/Brazzaville", // Link Africa/Lagos Africa/Brazzaville # Rep. of the Congo
+ "Africa/Douala", // Link Africa/Lagos Africa/Douala # Cameroon
+ "Africa/Kinshasa", // Link Africa/Lagos Africa/Kinshasa # Dem. Rep. of the Congo (west)
+ "Africa/Libreville", // Link Africa/Lagos Africa/Libreville # Gabon
+ "Africa/Luanda", // Link Africa/Lagos Africa/Luanda # Angola
+ "Africa/Malabo", // Link Africa/Lagos Africa/Malabo # Equatorial Guinea
+ "Africa/Niamey", // Link Africa/Lagos Africa/Niamey # Niger
+ "Africa/Porto-Novo", // Link Africa/Lagos Africa/Porto-Novo # Benin
+ "Africa/Maseru", // Link Africa/Johannesburg Africa/Maseru # Lesotho
+ "Africa/Mbabane", // Link Africa/Johannesburg Africa/Mbabane # Eswatini
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-asia.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-asia.js
new file mode 100644
index 0000000000..80b5b3e62d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-asia.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Europe/Nicosia", // Link Asia/Nicosia Europe/Nicosia
+ "Asia/Bahrain", // Link Asia/Qatar Asia/Bahrain
+ "Antarctica/Syowa", // Link Asia/Riyadh Antarctica/Syowa
+ "Asia/Aden", // Link Asia/Riyadh Asia/Aden # Yemen
+ "Asia/Kuwait", // Link Asia/Riyadh Asia/Kuwait
+ "Asia/Phnom_Penh", // Link Asia/Bangkok Asia/Phnom_Penh # Cambodia
+ "Asia/Vientiane", // Link Asia/Bangkok Asia/Vientiane # Laos
+ "Asia/Muscat", // Link Asia/Dubai Asia/Muscat # Oman
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-australasia.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-australasia.js
new file mode 100644
index 0000000000..b24164d8f3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-australasia.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Pacific/Saipan", // Link Pacific/Guam Pacific/Saipan # N Mariana Is
+ "Antarctica/McMurdo", // Link Pacific/Auckland Antarctica/McMurdo
+ "Antarctica/DumontDUrville", // Link Pacific/Port_Moresby Antarctica/DumontDUrville
+ "Pacific/Midway", // Link Pacific/Pago_Pago Pacific/Midway # in US minor outlying islands
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-backward.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-backward.js
new file mode 100644
index 0000000000..afab22aa45
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-backward.js
@@ -0,0 +1,143 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Africa/Asmera", // Link Africa/Nairobi Africa/Asmera
+ "Africa/Timbuktu", // Link Africa/Abidjan Africa/Timbuktu
+ "America/Argentina/ComodRivadavia", // Link America/Argentina/Catamarca America/Argentina/ComodRivadavia
+ "America/Atka", // Link America/Adak America/Atka
+ "America/Buenos_Aires", // Link America/Argentina/Buenos_Aires America/Buenos_Aires
+ "America/Catamarca", // Link America/Argentina/Catamarca America/Catamarca
+ "America/Coral_Harbour", // Link America/Panama America/Coral_Harbour
+ "America/Cordoba", // Link America/Argentina/Cordoba America/Cordoba
+ "America/Ensenada", // Link America/Tijuana America/Ensenada
+ "America/Fort_Wayne", // Link America/Indiana/Indianapolis America/Fort_Wayne
+ "America/Godthab", // Link America/Nuuk America/Godthab
+ "America/Indianapolis", // Link America/Indiana/Indianapolis America/Indianapolis
+ "America/Jujuy", // Link America/Argentina/Jujuy America/Jujuy
+ "America/Knox_IN", // Link America/Indiana/Knox America/Knox_IN
+ "America/Louisville", // Link America/Kentucky/Louisville America/Louisville
+ "America/Mendoza", // Link America/Argentina/Mendoza America/Mendoza
+ "America/Montreal", // Link America/Toronto America/Montreal
+ "America/Porto_Acre", // Link America/Rio_Branco America/Porto_Acre
+ "America/Rosario", // Link America/Argentina/Cordoba America/Rosario
+ "America/Santa_Isabel", // Link America/Tijuana America/Santa_Isabel
+ "America/Shiprock", // Link America/Denver America/Shiprock
+ "America/Virgin", // Link America/Puerto_Rico America/Virgin
+ "Antarctica/South_Pole", // Link Pacific/Auckland Antarctica/South_Pole
+ "Asia/Ashkhabad", // Link Asia/Ashgabat Asia/Ashkhabad
+ "Asia/Calcutta", // Link Asia/Kolkata Asia/Calcutta
+ "Asia/Chongqing", // Link Asia/Shanghai Asia/Chongqing
+ "Asia/Chungking", // Link Asia/Shanghai Asia/Chungking
+ "Asia/Dacca", // Link Asia/Dhaka Asia/Dacca
+ "Asia/Harbin", // Link Asia/Shanghai Asia/Harbin
+ "Asia/Kashgar", // Link Asia/Urumqi Asia/Kashgar
+ "Asia/Katmandu", // Link Asia/Kathmandu Asia/Katmandu
+ "Asia/Macao", // Link Asia/Macau Asia/Macao
+ "Asia/Rangoon", // Link Asia/Yangon Asia/Rangoon
+ "Asia/Saigon", // Link Asia/Ho_Chi_Minh Asia/Saigon
+ "Asia/Tel_Aviv", // Link Asia/Jerusalem Asia/Tel_Aviv
+ "Asia/Thimbu", // Link Asia/Thimphu Asia/Thimbu
+ "Asia/Ujung_Pandang", // Link Asia/Makassar Asia/Ujung_Pandang
+ "Asia/Ulan_Bator", // Link Asia/Ulaanbaatar Asia/Ulan_Bator
+ "Atlantic/Faeroe", // Link Atlantic/Faroe Atlantic/Faeroe
+ "Atlantic/Jan_Mayen", // Link Europe/Oslo Atlantic/Jan_Mayen
+ "Australia/ACT", // Link Australia/Sydney Australia/ACT
+ "Australia/Canberra", // Link Australia/Sydney Australia/Canberra
+ "Australia/Currie", // Link Australia/Hobart Australia/Currie
+ "Australia/LHI", // Link Australia/Lord_Howe Australia/LHI
+ "Australia/NSW", // Link Australia/Sydney Australia/NSW
+ "Australia/North", // Link Australia/Darwin Australia/North
+ "Australia/Queensland", // Link Australia/Brisbane Australia/Queensland
+ "Australia/South", // Link Australia/Adelaide Australia/South
+ "Australia/Tasmania", // Link Australia/Hobart Australia/Tasmania
+ "Australia/Victoria", // Link Australia/Melbourne Australia/Victoria
+ "Australia/West", // Link Australia/Perth Australia/West
+ "Australia/Yancowinna", // Link Australia/Broken_Hill Australia/Yancowinna
+ "Brazil/Acre", // Link America/Rio_Branco Brazil/Acre
+ "Brazil/DeNoronha", // Link America/Noronha Brazil/DeNoronha
+ "Brazil/East", // Link America/Sao_Paulo Brazil/East
+ "Brazil/West", // Link America/Manaus Brazil/West
+ "Canada/Atlantic", // Link America/Halifax Canada/Atlantic
+ "Canada/Central", // Link America/Winnipeg Canada/Central
+ "Canada/Eastern", // Link America/Toronto Canada/Eastern
+ "Canada/Mountain", // Link America/Edmonton Canada/Mountain
+ "Canada/Newfoundland", // Link America/St_Johns Canada/Newfoundland
+ "Canada/Pacific", // Link America/Vancouver Canada/Pacific
+ "Canada/Saskatchewan", // Link America/Regina Canada/Saskatchewan
+ "Canada/Yukon", // Link America/Whitehorse Canada/Yukon
+ "Chile/Continental", // Link America/Santiago Chile/Continental
+ "Chile/EasterIsland", // Link Pacific/Easter Chile/EasterIsland
+ "Cuba", // Link America/Havana Cuba
+ "Egypt", // Link Africa/Cairo Egypt
+ "Eire", // Link Europe/Dublin Eire
+ "Etc/UCT", // Link Etc/UTC Etc/UCT
+ "Europe/Belfast", // Link Europe/London Europe/Belfast
+ "Europe/Kiev", // Link Europe/Kyiv Europe/Kiev
+ "Europe/Tiraspol", // Link Europe/Chisinau Europe/Tiraspol
+ "GB", // Link Europe/London GB
+ "GB-Eire", // Link Europe/London GB-Eire
+ "GMT+0", // Link Etc/GMT GMT+0
+ "GMT-0", // Link Etc/GMT GMT-0
+ "GMT0", // Link Etc/GMT GMT0
+ "Greenwich", // Link Etc/GMT Greenwich
+ "Hongkong", // Link Asia/Hong_Kong Hongkong
+ "Iceland", // Link Atlantic/Reykjavik Iceland
+ "Iran", // Link Asia/Tehran Iran
+ "Israel", // Link Asia/Jerusalem Israel
+ "Jamaica", // Link America/Jamaica Jamaica
+ "Japan", // Link Asia/Tokyo Japan
+ "Kwajalein", // Link Pacific/Kwajalein Kwajalein
+ "Libya", // Link Africa/Tripoli Libya
+ "Mexico/BajaNorte", // Link America/Tijuana Mexico/BajaNorte
+ "Mexico/BajaSur", // Link America/Mazatlan Mexico/BajaSur
+ "Mexico/General", // Link America/Mexico_City Mexico/General
+ "NZ", // Link Pacific/Auckland NZ
+ "NZ-CHAT", // Link Pacific/Chatham NZ-CHAT
+ "Navajo", // Link America/Denver Navajo
+ "PRC", // Link Asia/Shanghai PRC
+ "Pacific/Enderbury", // Link Pacific/Kanton Pacific/Enderbury
+ "Pacific/Johnston", // Link Pacific/Honolulu Pacific/Johnston
+ "Pacific/Ponape", // Link Pacific/Pohnpei Pacific/Ponape
+ "Pacific/Samoa", // Link Pacific/Pago_Pago Pacific/Samoa
+ "Pacific/Truk", // Link Pacific/Chuuk Pacific/Truk
+ "Pacific/Yap", // Link Pacific/Chuuk Pacific/Yap
+ "Poland", // Link Europe/Warsaw Poland
+ "Portugal", // Link Europe/Lisbon Portugal
+ "ROC", // Link Asia/Taipei ROC
+ "ROK", // Link Asia/Seoul ROK
+ "Singapore", // Link Asia/Singapore Singapore
+ "Turkey", // Link Europe/Istanbul Turkey
+ "UCT", // Link Etc/UTC UCT
+ "US/Alaska", // Link America/Anchorage US/Alaska
+ "US/Aleutian", // Link America/Adak US/Aleutian
+ "US/Arizona", // Link America/Phoenix US/Arizona
+ "US/Central", // Link America/Chicago US/Central
+ "US/East-Indiana", // Link America/Indiana/Indianapolis US/East-Indiana
+ "US/Eastern", // Link America/New_York US/Eastern
+ "US/Hawaii", // Link Pacific/Honolulu US/Hawaii
+ "US/Indiana-Starke", // Link America/Indiana/Knox US/Indiana-Starke
+ "US/Michigan", // Link America/Detroit US/Michigan
+ "US/Mountain", // Link America/Denver US/Mountain
+ "US/Pacific", // Link America/Los_Angeles US/Pacific
+ "US/Samoa", // Link Pacific/Pago_Pago US/Samoa
+ "UTC", // Link Etc/UTC UTC
+ "Universal", // Link Etc/UTC Universal
+ "W-SU", // Link Europe/Moscow W-SU
+ "Zulu", // Link Etc/UTC Zulu
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-backzone.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-backzone.js
new file mode 100644
index 0000000000..90eb8dca19
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-backzone.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Africa/Asmera", // Link Africa/Asmara Africa/Asmera
+ "America/Kralendijk", // Link America/Curacao America/Kralendijk
+ "America/Lower_Princes", // Link America/Curacao America/Lower_Princes
+ "America/Marigot", // Link America/Port_of_Spain America/Marigot
+ "America/St_Barthelemy", // Link America/Port_of_Spain America/St_Barthelemy
+ "America/Virgin", // Link America/St_Thomas America/Virgin
+ "Antarctica/South_Pole", // Link Antarctica/McMurdo Antarctica/South_Pole
+ "Asia/Chungking", // Link Asia/Chongqing Asia/Chungking
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-etcetera.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-etcetera.js
new file mode 100644
index 0000000000..e67c2bea7a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-etcetera.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "GMT", // Link Etc/GMT GMT
+ "Etc/Universal", // Link Etc/UTC Etc/Universal
+ "Etc/Zulu", // Link Etc/UTC Etc/Zulu
+ "Etc/Greenwich", // Link Etc/GMT Etc/Greenwich
+ "Etc/GMT-0", // Link Etc/GMT Etc/GMT-0
+ "Etc/GMT+0", // Link Etc/GMT Etc/GMT+0
+ "Etc/GMT0", // Link Etc/GMT Etc/GMT0
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-europe.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-europe.js
new file mode 100644
index 0000000000..81149e8d12
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-europe.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Europe/Jersey", // Link Europe/London Europe/Jersey
+ "Europe/Guernsey", // Link Europe/London Europe/Guernsey
+ "Europe/Isle_of_Man", // Link Europe/London Europe/Isle_of_Man
+ "Europe/Mariehamn", // Link Europe/Helsinki Europe/Mariehamn
+ "Europe/Busingen", // Link Europe/Zurich Europe/Busingen
+ "Europe/Vatican", // Link Europe/Rome Europe/Vatican
+ "Europe/San_Marino", // Link Europe/Rome Europe/San_Marino
+ "Europe/Vaduz", // Link Europe/Zurich Europe/Vaduz
+ "Arctic/Longyearbyen", // Link Europe/Oslo Arctic/Longyearbyen
+ "Europe/Ljubljana", // Link Europe/Belgrade Europe/Ljubljana # Slovenia
+ "Europe/Podgorica", // Link Europe/Belgrade Europe/Podgorica # Montenegro
+ "Europe/Sarajevo", // Link Europe/Belgrade Europe/Sarajevo # Bosnia and Herzegovina
+ "Europe/Skopje", // Link Europe/Belgrade Europe/Skopje # North Macedonia
+ "Europe/Zagreb", // Link Europe/Belgrade Europe/Zagreb # Croatia
+ "Europe/Bratislava", // Link Europe/Prague Europe/Bratislava
+ "Asia/Istanbul", // Link Europe/Istanbul Asia/Istanbul # Istanbul is in both continents.
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/links-northamerica.js b/js/src/tests/test262/intl402/Temporal/TimeZone/links-northamerica.js
new file mode 100644
index 0000000000..c1b24fe974
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/links-northamerica.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts link names as its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "America/Creston", // Link America/Phoenix America/Creston
+ "America/Nassau", // Link America/Toronto America/Nassau
+ "America/Atikokan", // Link America/Panama America/Atikokan
+ "America/Cayman", // Link America/Panama America/Cayman
+ "America/Anguilla", // Link America/Puerto_Rico America/Anguilla
+ "America/Antigua", // Link America/Puerto_Rico America/Antigua
+ "America/Aruba", // Link America/Puerto_Rico America/Aruba
+ "America/Curacao", // Link America/Puerto_Rico America/Curacao
+ "America/Blanc-Sablon", // Link America/Puerto_Rico America/Blanc-Sablon # Quebec (Lower North Shore)
+ "America/Dominica", // Link America/Puerto_Rico America/Dominica
+ "America/Grenada", // Link America/Puerto_Rico America/Grenada
+ "America/Guadeloupe", // Link America/Puerto_Rico America/Guadeloupe
+ "America/Kralendijk", // Link America/Puerto_Rico America/Kralendijk # Caribbean Netherlands
+ "America/Lower_Princes", // Link America/Puerto_Rico America/Lower_Princes # Sint Maarten
+ "America/Marigot", // Link America/Puerto_Rico America/Marigot # St Martin (French part)
+ "America/Montserrat", // Link America/Puerto_Rico America/Montserrat
+ "America/Port_of_Spain", // Link America/Puerto_Rico America/Port_of_Spain # Trinidad & Tobago
+ "America/St_Barthelemy", // Link America/Puerto_Rico America/St_Barthelemy # St Barthélemy
+ "America/St_Kitts", // Link America/Puerto_Rico America/St_Kitts # St Kitts & Nevis
+ "America/St_Lucia", // Link America/Puerto_Rico America/St_Lucia
+ "America/St_Thomas", // Link America/Puerto_Rico America/St_Thomas # Virgin Islands (US)
+ "America/St_Vincent", // Link America/Puerto_Rico America/St_Vincent
+ "America/Tortola", // Link America/Puerto_Rico America/Tortola # Virgin Islands (UK)
+];
+
+for (let id of testCases) {
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/non-canonical-utc.js b/js/src/tests/test262/intl402/Temporal/TimeZone/non-canonical-utc.js
new file mode 100644
index 0000000000..468ecd2afd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/non-canonical-utc.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor canonicalises its input.
+features: [Temporal]
+---*/
+
+const testCases = [
+ "Etc/GMT",
+ "Etc/GMT+0",
+ "Etc/GMT-0",
+ "Etc/GMT0",
+ "Etc/Greenwich",
+ "Etc/UCT",
+ "Etc/UTC",
+ "Etc/Universal",
+ "Etc/Zulu",
+];
+
+for (let id of testCases) {
+ let tz = new Temporal.TimeZone(id);
+
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-object.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-object.js
new file mode 100644
index 0000000000..551550d21a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-object.js
@@ -0,0 +1,84 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Objects with IANA IDs are compared case-insensitively with their canonical IDs
+features: [Temporal]
+---*/
+
+class CustomTimeZone extends Temporal.TimeZone {
+ constructor(id) {
+ super("UTC");
+ this._id = id;
+ }
+ get id() {
+ return this._id;
+ }
+}
+
+const classInstancesIANA = [
+ new Temporal.TimeZone("Asia/Calcutta"),
+ new CustomTimeZone("Asia/Calcutta"),
+ new Temporal.TimeZone("Asia/Kolkata"),
+ new CustomTimeZone("Asia/Kolkata"),
+ new CustomTimeZone("ASIA/calcutta"),
+ new CustomTimeZone("Asia/KOLKATA")
+];
+
+const plainObjectsIANA = [
+ { id: "Asia/Calcutta", getPossibleInstantsFor: null, getOffsetNanosecondsFor: null },
+ { id: "Asia/Kolkata", getPossibleInstantsFor: null, getOffsetNanosecondsFor: null },
+ { id: "ASIA/calcutta", getPossibleInstantsFor: null, getOffsetNanosecondsFor: null },
+ { id: "asia/kolkatA", getPossibleInstantsFor: null, getOffsetNanosecondsFor: null }
+];
+
+for (const object1 of classInstancesIANA) {
+ for (const object2 of classInstancesIANA) {
+ assert.sameValue(object1.equals(object2), true, `Receiver ${object1.id} should not equal argument ${object2.id}`);
+ }
+ for (const object2 of plainObjectsIANA) {
+ assert.sameValue(object1.equals(object2), true, `Receiver ${object2.id} should not equal argument ${object1.id}`);
+ }
+}
+
+const classInstancesIANADifferentCanonical = [
+ new Temporal.TimeZone("Asia/Colombo"),
+ new CustomTimeZone("Asia/Colombo"),
+ new Temporal.TimeZone("ASIA/colombo"),
+ new CustomTimeZone("ASIA/colombo")
+];
+
+for (const object1 of classInstancesIANADifferentCanonical) {
+ for (const object2 of classInstancesIANA) {
+ assert.sameValue(object1.equals(object2), false, `Receiver ${object1.id} should not equal argument ${object2.id}`);
+ assert.sameValue(object2.equals(object1), false, `Receiver ${object2.id} should not equal argument ${object1.id}`);
+ }
+ for (const object2 of plainObjectsIANA) {
+ assert.sameValue(object1.equals(object2), false, `Receiver ${object1.id} should not equal argument ${object2.id}`);
+ assert.sameValue(
+ object1.equals(object2.id),
+ false,
+ `Receiver ${object1.id} should not equal argument ${object2.id}`
+ );
+ }
+}
+
+const classInstancesCustomNotIANA = [new CustomTimeZone("Moon/Cheese")];
+for (const object1 of classInstancesCustomNotIANA) {
+ for (const object2 of classInstancesIANA) {
+ assert.sameValue(object1.equals(object2), false, `Receiver ${object1.id} should not equal argument ${object2.id}`);
+ assert.sameValue(object2.equals(object1), false, `Receiver ${object2.id} should not equal argument ${object1.id}`);
+ }
+ for (const object2 of plainObjectsIANA) {
+ assert.sameValue(object1.equals(object2), false, `Receiver ${object1.id} should not equal argument ${object2.id}`);
+ assert.sameValue(
+ object1.equals(object2.id),
+ false,
+ `Receiver ${object1.id} should not equal argument ${object2.id}`
+ );
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-valid.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-valid.js
new file mode 100644
index 0000000000..836a3eff24
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/argument-valid.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Built-in time zones are parsed correctly out of valid strings
+features: [Temporal]
+---*/
+
+const valids = [
+ ["Africa/CAIRO", "Africa/Cairo"],
+ ["Asia/Ulan_Bator", "Asia/Ulaanbaatar"],
+ ["etc/gmt", "Etc/GMT"],
+ ["1994-11-05T08:15:30-05:00[America/New_York]", "America/New_York"],
+ ["1994-11-05T08:15:30+05:30[Asia/Calcutta]", "Asia/Calcutta"],
+ ["1994-11-05T08:15:30+05:30[Asia/Calcutta]", "Asia/Kolkata"],
+ ["1994-11-05T08:15:30+05:30[Asia/Kolkata]", "Asia/Calcutta"],
+ ["1994-11-05T08:15:30+05:30[Asia/Kolkata]", "Asia/Kolkata"],
+];
+
+for (const [valid, canonical = valid] of valids) {
+ const tzValid = Temporal.TimeZone.from(canonical);
+ const tzCanonical = Temporal.TimeZone.from(canonical);
+ assert.sameValue(tzValid.equals(tzCanonical), true);
+ assert.sameValue(tzCanonical.equals(tzValid), true);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-iana-names.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-iana-names.js
new file mode 100644
index 0000000000..6be500a228
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-iana-names.js
@@ -0,0 +1,50 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Canonicalizes to evaluate time zone equality
+features: [Temporal]
+---*/
+
+const neverEqual = new Temporal.TimeZone('Asia/Tokyo');
+const zdt = new Temporal.ZonedDateTime(0n, 'America/Los_Angeles');
+const ids = [
+ ['America/Atka', 'America/Adak'],
+ ['America/Knox_IN', 'America/Indiana/Knox'],
+ ['Asia/Ashkhabad', 'Asia/Ashgabat'],
+ ['Asia/Dacca', 'Asia/Dhaka'],
+ ['Asia/Istanbul', 'Europe/Istanbul'],
+ ['Asia/Macao', 'Asia/Macau'],
+ ['Asia/Thimbu', 'Asia/Thimphu'],
+ ['Asia/Ujung_Pandang', 'Asia/Makassar'],
+ ['Asia/Ulan_Bator', 'Asia/Ulaanbaatar']
+];
+
+for (const [identifier, primaryIdentifier] of ids) {
+ const tz1 = new Temporal.TimeZone(identifier);
+ const tz2 = new Temporal.TimeZone(primaryIdentifier);
+
+ // compare objects
+ assert.sameValue(tz1.equals(tz2), true);
+ assert.sameValue(tz2.equals(tz1), true);
+ assert.sameValue(tz1.equals(neverEqual), false);
+
+ // compare string IDs
+ assert.sameValue(tz1.equals(tz2.id), true);
+ assert.sameValue(tz2.equals(tz1.id), true);
+ assert.sameValue(tz1.equals(neverEqual.id), false);
+
+ // compare ZonedDateTime instances
+ assert.sameValue(tz1.equals(zdt.withTimeZone(tz2)), true);
+ assert.sameValue(tz2.equals(zdt.withTimeZone(tz1)), true);
+ assert.sameValue(tz1.equals(zdt.withTimeZone(neverEqual)), false);
+
+ // compare IXDTF strings
+ assert.sameValue(tz1.equals(zdt.withTimeZone(tz2).toString()), true);
+ assert.sameValue(tz2.equals(zdt.withTimeZone(tz1).toString()), true);
+ assert.sameValue(tz1.equals(zdt.withTimeZone(neverEqual).toString()), false);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-not-equal.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-not-equal.js
new file mode 100644
index 0000000000..f37c49054d
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/canonical-not-equal.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Canonical time zone identifiers are never equal to each other
+features: [Temporal]
+---*/
+
+// supportedValuesOf only returns canonical IDs
+const ids = Intl.supportedValuesOf("timeZone");
+
+const forEachDistinctPair = (array, func) => {
+ for (let i = 0; i < array.length; i++) {
+ for (let j = i + 1; j < array.length; j++) {
+ func(array[i], array[j]);
+ }
+ }
+};
+
+forEachDistinctPair(ids, (id1, id2) => {
+ const tz = new Temporal.TimeZone(id1);
+ assert.sameValue(tz.equals(id2), false);
+})
+
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/offset-and-iana.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/offset-and-iana.js
new file mode 100644
index 0000000000..af9579cf53
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/offset-and-iana.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: Offset string time zones compare as expected
+features: [Temporal]
+---*/
+
+const zdt = new Temporal.ZonedDateTime(0n, "America/Los_Angeles");
+const otz1 = new Temporal.TimeZone("+05:30");
+const otz2 = new Temporal.TimeZone("+0530");
+const tz = new Temporal.TimeZone("Asia/Kolkata");
+assert.sameValue(otz1.equals(otz2), true);
+assert.sameValue(otz2.equals(otz1), true);
+assert.sameValue(otz1.equals("+05:30"), true);
+assert.sameValue(otz1.equals(zdt.withTimeZone(otz2)), true);
+assert.sameValue(otz1.equals(zdt.withTimeZone(otz2).toString()), true);
+assert.sameValue(otz1.equals(tz), false);
+assert.sameValue(otz1.equals("Asia/Kolkata"), false);
+assert.sameValue(otz1.equals(zdt.withTimeZone(tz)), false);
+assert.sameValue(otz1.equals(zdt.withTimeZone(tz).toString()), false);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/timezone-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/timezone-case-insensitive.js
new file mode 100644
index 0000000000..f586342153
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/equals/timezone-case-insensitive.js
@@ -0,0 +1,627 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.from
+description: Time zone names are compared case-insensitively
+features: [Temporal]
+---*/
+
+const timeZoneIdentifiers = [
+ // IANA TZDB Zone names
+ 'Africa/Abidjan',
+ 'Africa/Algiers',
+ 'Africa/Bissau',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/El_Aaiun',
+ 'Africa/Johannesburg',
+ 'Africa/Juba',
+ 'Africa/Khartoum',
+ 'Africa/Lagos',
+ 'Africa/Maputo',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Sao_Tome',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Asuncion',
+ 'America/Bahia',
+ 'America/Bahia_Banderas',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Cayenne',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ // 'America/Ciudad_Juarez' // uncomment after Node supports this ID added in TZDB 2022g
+ 'America/Costa_Rica',
+ 'America/Cuiaba',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Fort_Nelson',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Metlakatla',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/New_York',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Beulah',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Nuuk',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port-au-Prince',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Punta_Arenas',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/Sitka',
+ 'America/St_Johns',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Vancouver',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/Macquarie',
+ 'Antarctica/Mawson',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/Troll',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Atyrau',
+ 'Asia/Baghdad',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Barnaul',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Chita',
+ 'Asia/Choibalsan',
+ 'Asia/Colombo',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Famagusta',
+ 'Asia/Gaza',
+ 'Asia/Hebron',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kathmandu',
+ 'Asia/Khandyga',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuching',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qostanay',
+ 'Asia/Qyzylorda',
+ 'Asia/Riyadh',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Srednekolymsk',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Tomsk',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Urumqi',
+ 'Asia/Ust-Nera',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yangon',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faroe',
+ 'Atlantic/Madeira',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/Stanley',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/Perth',
+ 'Australia/Sydney',
+ 'CET',
+ 'CST6CDT',
+ 'EET',
+ 'EST',
+ 'EST5EDT',
+ 'Etc/GMT',
+ 'Etc/GMT+1',
+ 'Etc/GMT+10',
+ 'Etc/GMT+11',
+ 'Etc/GMT+12',
+ 'Etc/GMT+2',
+ 'Etc/GMT+3',
+ 'Etc/GMT+4',
+ 'Etc/GMT+5',
+ 'Etc/GMT+6',
+ 'Etc/GMT+7',
+ 'Etc/GMT+8',
+ 'Etc/GMT+9',
+ 'Etc/GMT-1',
+ 'Etc/GMT-10',
+ 'Etc/GMT-11',
+ 'Etc/GMT-12',
+ 'Etc/GMT-13',
+ 'Etc/GMT-14',
+ 'Etc/GMT-2',
+ 'Etc/GMT-3',
+ 'Etc/GMT-4',
+ 'Etc/GMT-5',
+ 'Etc/GMT-6',
+ 'Etc/GMT-7',
+ 'Etc/GMT-8',
+ 'Etc/GMT-9',
+ 'Etc/UTC',
+ 'Europe/Andorra',
+ 'Europe/Astrakhan',
+ 'Europe/Athens',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Chisinau',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Helsinki',
+ 'Europe/Istanbul',
+ 'Europe/Kaliningrad',
+ 'Europe/Kirov',
+ 'Europe/Kyiv',
+ 'Europe/Lisbon',
+ 'Europe/London',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Minsk',
+ 'Europe/Moscow',
+ 'Europe/Paris',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/Saratov',
+ 'Europe/Simferopol',
+ 'Europe/Sofia',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Ulyanovsk',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zurich',
+ 'HST',
+ 'Indian/Chagos',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'MET',
+ 'MST',
+ 'MST7MDT',
+ 'PST8PDT',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Bougainville',
+ 'Pacific/Chatham',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Kanton',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Marquesas',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+
+ // IANA TZDB Link names
+ 'WET',
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Kampala',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Timbuktu',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Argentina/ComodRivadavia',
+ 'America/Aruba',
+ 'America/Atikokan',
+ 'America/Atka',
+ 'America/Blanc-Sablon',
+ 'America/Buenos_Aires',
+ 'America/Catamarca',
+ 'America/Cayman',
+ 'America/Coral_Harbour',
+ 'America/Cordoba',
+ 'America/Creston',
+ 'America/Curacao',
+ 'America/Dominica',
+ 'America/Ensenada',
+ 'America/Fort_Wayne',
+ 'America/Godthab',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Indianapolis',
+ 'America/Jujuy',
+ 'America/Knox_IN',
+ 'America/Kralendijk',
+ 'America/Louisville',
+ 'America/Lower_Princes',
+ 'America/Marigot',
+ 'America/Mendoza',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/Nipigon',
+ 'America/Pangnirtung',
+ 'America/Port_of_Spain',
+ 'America/Porto_Acre',
+ 'America/Rainy_River',
+ 'America/Rosario',
+ 'America/Santa_Isabel',
+ 'America/Shiprock',
+ 'America/St_Barthelemy',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Thunder_Bay',
+ 'America/Tortola',
+ 'America/Virgin',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/McMurdo',
+ 'Antarctica/South_Pole',
+ 'Antarctica/Syowa',
+ 'Antarctica/Vostok',
+ 'Arctic/Longyearbyen',
+ 'Asia/Aden',
+ 'Asia/Ashkhabad',
+ 'Asia/Bahrain',
+ 'Asia/Brunei',
+ 'Asia/Calcutta',
+ 'Asia/Chongqing',
+ 'Asia/Chungking',
+ 'Asia/Dacca',
+ 'Asia/Harbin',
+ 'Asia/Istanbul',
+ 'Asia/Kashgar',
+ 'Asia/Katmandu',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuwait',
+ 'Asia/Macao',
+ 'Asia/Muscat',
+ 'Asia/Phnom_Penh',
+ 'Asia/Rangoon',
+ 'Asia/Saigon',
+ 'Asia/Tel_Aviv',
+ 'Asia/Thimbu',
+ 'Asia/Ujung_Pandang',
+ 'Asia/Ulan_Bator',
+ 'Asia/Vientiane',
+ 'Atlantic/Faeroe',
+ 'Atlantic/Jan_Mayen',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/St_Helena',
+ 'Australia/ACT',
+ 'Australia/Canberra',
+ 'Australia/Currie',
+ 'Australia/LHI',
+ 'Australia/NSW',
+ 'Australia/North',
+ 'Australia/Queensland',
+ 'Australia/South',
+ 'Australia/Tasmania',
+ 'Australia/Victoria',
+ 'Australia/West',
+ 'Australia/Yancowinna',
+ 'Brazil/Acre',
+ 'Brazil/DeNoronha',
+ 'Brazil/East',
+ 'Brazil/West',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Canada/Saskatchewan',
+ 'Canada/Yukon',
+ 'Chile/Continental',
+ 'Chile/EasterIsland',
+ 'Cuba',
+ 'Egypt',
+ 'Eire',
+ 'Etc/GMT+0',
+ 'Etc/GMT-0',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'Etc/UCT',
+ 'Etc/Universal',
+ 'Etc/Zulu',
+ 'Europe/Amsterdam',
+ 'Europe/Belfast',
+ 'Europe/Bratislava',
+ 'Europe/Busingen',
+ 'Europe/Copenhagen',
+ 'Europe/Guernsey',
+ 'Europe/Isle_of_Man',
+ 'Europe/Jersey',
+ 'Europe/Kiev',
+ 'Europe/Ljubljana',
+ 'Europe/Luxembourg',
+ 'Europe/Mariehamn',
+ 'Europe/Monaco',
+ 'Europe/Nicosia',
+ 'Europe/Oslo',
+ 'Europe/Podgorica',
+ 'Europe/San_Marino',
+ 'Europe/Sarajevo',
+ 'Europe/Skopje',
+ 'Europe/Stockholm',
+ 'Europe/Tiraspol',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vatican',
+ 'Europe/Zagreb',
+ 'Europe/Zaporozhye',
+ 'GB',
+ 'GB-Eire',
+ 'GMT',
+ 'GMT+0',
+ 'GMT-0',
+ 'GMT0',
+ 'Greenwich',
+ 'Hongkong',
+ 'Iceland',
+ 'Indian/Antananarivo',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Iran',
+ 'Israel',
+ 'Jamaica',
+ 'Japan',
+ 'Kwajalein',
+ 'Libya',
+ 'Mexico/BajaNorte',
+ 'Mexico/BajaSur',
+ 'Mexico/General',
+ 'NZ',
+ 'NZ-CHAT',
+ 'Navajo',
+ 'PRC',
+ 'Pacific/Chuuk',
+ 'Pacific/Enderbury',
+ 'Pacific/Funafuti',
+ 'Pacific/Johnston',
+ 'Pacific/Majuro',
+ 'Pacific/Midway',
+ 'Pacific/Pohnpei',
+ 'Pacific/Ponape',
+ 'Pacific/Saipan',
+ 'Pacific/Samoa',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'Pacific/Yap',
+ 'Poland',
+ 'Portugal',
+ 'ROC',
+ 'ROK',
+ 'Singapore',
+ 'Turkey',
+ 'UCT',
+ 'US/Alaska',
+ 'US/Aleutian',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/East-Indiana',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Indiana-Starke',
+ 'US/Michigan',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'US/Samoa',
+ 'UTC',
+ 'Universal',
+ 'W-SU',
+ 'Zulu'
+];
+
+// We want to test all available named time zone identifiers (both primary and non-primary),
+// but no ECMAScript built-in API exposes that list. So we use a union of two sources:
+// 1. A hard-coded list of Zone and Link identifiers from the 2022g version of IANA TZDB.
+// 2. Canonical IDs exposed by Intl.supportedValuesOf('timeZone'), which ensures that IDs
+// added to TZDB later than 2022g will be tested. (New IDs are almost always added as primary.)
+const ids = [...new Set([...timeZoneIdentifiers, ...Intl.supportedValuesOf('timeZone')])];
+for (const id of ids) {
+ const lower = id.toLowerCase();
+ const upper = id.toUpperCase();
+ const tz = new Temporal.TimeZone(id);
+ assert.sameValue(tz.equals(upper), true, `Time zone "${id}" compared to string "${upper}"`);
+ assert.sameValue(tz.equals(lower), true, `Time zone "${id}" compared to string "${lower}"`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..0c644cf356
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.timezone.prototype.getinstantfor
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("UTC");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.getInstantFor({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.getInstantFor({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getInstantFor/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/subtract-second-and-nanosecond-from-last-transition.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/subtract-second-and-nanosecond-from-last-transition.js
new file mode 100644
index 0000000000..b1b157afb4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/subtract-second-and-nanosecond-from-last-transition.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getnexttransition
+description: >
+ Compute next transition when seconds resp. nanoseconds are subtracted from the last transition.
+features: [Temporal]
+---*/
+
+// From <https://github.com/eggert/tz/blob/main/europe>:
+//
+// # Zone NAME STDOFF RULES FORMAT [UNTIL]
+// Zone Europe/Paris 0:09:21 - LMT 1891 Mar 15 0:01
+// 0:09:21 - PMT 1911 Mar 11 0:01 # Paris MT
+
+let tz = new Temporal.TimeZone("Europe/Paris");
+
+let zdt = new Temporal.PlainDateTime(1800, 1, 1).toZonedDateTime(tz);
+assert.sameValue(zdt.toString(), "1800-01-01T00:00:00+00:09[Europe/Paris]");
+assert.sameValue(zdt.offsetNanoseconds, (9 * 60 + 21) * 1_000_000_000);
+
+// Ensure the first transition was correctly computed.
+let first = tz.getNextTransition(zdt);
+assert.sameValue(first.toString(), "1911-03-10T23:50:39Z");
+assert.sameValue(new Temporal.ZonedDateTime(first.epochNanoseconds, tz).toString(),
+ "1911-03-10T23:50:39+00:00[Europe/Paris]");
+
+let next;
+
+// Compute the next transition starting from the first transition minus 1s.
+let firstMinus1s = first.add({seconds: -1});
+assert.sameValue(firstMinus1s.toString(), "1911-03-10T23:50:38Z");
+assert.sameValue(new Temporal.ZonedDateTime(firstMinus1s.epochNanoseconds, tz).toString(),
+ "1911-03-10T23:59:59+00:09[Europe/Paris]");
+assert.sameValue(new Temporal.ZonedDateTime(firstMinus1s.epochNanoseconds, tz).offsetNanoseconds,
+ (9 * 60 + 21) * 1_000_000_000);
+
+next = tz.getNextTransition(firstMinus1s);
+assert.sameValue(next.toString(), "1911-03-10T23:50:39Z");
+assert.sameValue(new Temporal.ZonedDateTime(next.epochNanoseconds, tz).toString(),
+ "1911-03-10T23:50:39+00:00[Europe/Paris]");
+
+// Compute the next transition starting from the first transition minus 1ns.
+let firstMinus1ns = first.add({nanoseconds: -1});
+assert.sameValue(firstMinus1ns.toString(), "1911-03-10T23:50:38.999999999Z");
+assert.sameValue(new Temporal.ZonedDateTime(firstMinus1ns.epochNanoseconds, tz).toString(),
+ "1911-03-10T23:59:59.999999999+00:09[Europe/Paris]");
+assert.sameValue(new Temporal.ZonedDateTime(firstMinus1ns.epochNanoseconds, tz).offsetNanoseconds,
+ (9 * 60 + 21) * 1_000_000_000);
+
+next = tz.getNextTransition(firstMinus1ns);
+assert.sameValue(next.toString(), "1911-03-10T23:50:39Z");
+assert.sameValue(new Temporal.ZonedDateTime(next.epochNanoseconds, tz).toString(),
+ "1911-03-10T23:50:39+00:00[Europe/Paris]");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js
new file mode 100644
index 0000000000..6eb8645589
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getnexttransition
+description: >
+ Test transitions at the instant boundaries.
+features: [Temporal, Intl-enumeration]
+---*/
+
+const min = new Temporal.Instant(-86_40000_00000_00000_00000n);
+const max = new Temporal.Instant(86_40000_00000_00000_00000n);
+
+for (let id of Intl.supportedValuesOf("timeZone")) {
+ let tz = new Temporal.TimeZone(id);
+
+ // If there's any next transition, it should be after |min|.
+ let next = tz.getNextTransition(min);
+ if (next) {
+ assert(next.epochNanoseconds > min.epochNanoseconds);
+ }
+
+ // There shouldn't be any next transition after |max|.
+ next = tz.getNextTransition(max);
+ assert.sameValue(next, null);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/instant-string.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/instant-string.js
new file mode 100644
index 0000000000..943fb73750
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/instant-string.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getoffsetnanosecondsfor
+description: Conversion of ISO date-time strings to Temporal.Instant instances
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("America/Vancouver");
+
+let str = "1970-01-01T00:00";
+assert.throws(RangeError, () => instance.getOffsetNanosecondsFor(str), "bare date-time string is not an instant");
+str = "1970-01-01T00:00[America/Vancouver]";
+assert.throws(RangeError, () => instance.getOffsetNanosecondsFor(str), "date-time + IANA annotation is not an instant");
+
+// The following are all valid strings so should not throw:
+
+const valids = [
+ "1970-01-01T00:00Z",
+ "1970-01-01T00:00+01:00",
+ "1970-01-01T00:00Z[America/Vancouver]",
+ "1970-01-01T00:00+01:00[America/Vancouver]",
+];
+for (const str of valids) {
+ const result = instance.getOffsetNanosecondsFor(str);
+ assert.sameValue(result, -28800e9);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/nanoseconds-subtracted-or-added-at-dst-transition.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/nanoseconds-subtracted-or-added-at-dst-transition.js
new file mode 100644
index 0000000000..06b28bf8f7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/nanoseconds-subtracted-or-added-at-dst-transition.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getoffsetnanosecondsfor
+description: >
+ Test offset when nanoseconds are subtracted or added from DST transition.
+features: [Temporal, exponentiation]
+---*/
+
+// From <https://github.com/eggert/tz/blob/main/northamerica>:
+//
+// # Rule NAME FROM TO - IN ON AT SAVE LETTER
+// Rule CA 1950 1966 - Apr lastSun 1:00 1:00 D
+//
+// # Zone NAME STDOFF RULES FORMAT [UNTIL]
+// Zone America/Los_Angeles -7:52:58 - LMT 1883 Nov 18 12:07:02
+// -8:00 US P%sT 1946
+// -8:00 CA P%sT 1967
+// -8:00 US P%sT
+
+let tz = new Temporal.TimeZone("America/Los_Angeles");
+let p = Temporal.Instant.from("1965-04-25T09:00:00Z");
+
+const nsPerHour = 60 * 60 * 1000**3;
+
+assert.sameValue(tz.getOffsetNanosecondsFor(p),
+ -7 * nsPerHour,
+ "DST transition");
+
+assert.sameValue(tz.getOffsetNanosecondsFor(p.add({nanoseconds: +1})),
+ -7 * nsPerHour,
+ "DST transition plus one nanosecond");
+
+assert.sameValue(tz.getOffsetNanosecondsFor(p.add({nanoseconds: -1})),
+ -8 * nsPerHour,
+ "DST transition minus one nanosecond");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/instant-string.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/instant-string.js
new file mode 100644
index 0000000000..4454bd6eb3
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/instant-string.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getoffsetstringfor
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("America/Vancouver");
+
+let str = "1970-01-01T00:00";
+assert.throws(RangeError, () => instance.getOffsetStringFor(str), "bare date-time string is not an instant");
+str = "1970-01-01T00:00[America/Vancouver]";
+assert.throws(RangeError, () => instance.getOffsetStringFor(str), "date-time + IANA annotation is not an instant");
+
+// The following are all valid strings so should not throw:
+
+const valids = [
+ "1970-01-01T00:00Z",
+ "1970-01-01T00:00+01:00",
+ "1970-01-01T00:00Z[America/Vancouver]",
+ "1970-01-01T00:00+01:00[America/Vancouver]",
+];
+for (const str of valids) {
+ const result = instance.getOffsetStringFor(str);
+ assert.sameValue(result, "-08:00");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getOffsetStringFor/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/basic.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/basic.js
new file mode 100644
index 0000000000..c10f4676c4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/basic.js
@@ -0,0 +1,81 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getplaindatetimefor
+description: Sample of results for IANA time zones
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+function test(epochNs, results) {
+ const instant = new Temporal.Instant(epochNs);
+ Object.entries(results).forEach(([id, expected]) => {
+ const tz = new Temporal.TimeZone(id);
+ const dt = tz.getPlainDateTimeFor(instant);
+ TemporalHelpers.assertPlainDateTime(dt, ...expected, `Local time of ${instant} in ${id}`);
+ });
+}
+
+// Unix epoch
+test(0n, {
+ 'America/Los_Angeles': [1969, 12, "M12", 31, 16, 0, 0, 0, 0, 0],
+ 'America/New_York': [1969, 12, "M12", 31, 19, 0, 0, 0, 0, 0],
+ 'Africa/Monrovia': [1969, 12, "M12", 31, 23, 15, 30, 0, 0, 0],
+ 'Europe/London': [1970, 1, "M01", 1, 1, 0, 0, 0, 0, 0],
+ 'Europe/Berlin': [1970, 1, "M01", 1, 1, 0, 0, 0, 0, 0],
+ 'Europe/Moscow': [1970, 1, "M01", 1, 3, 0, 0, 0, 0, 0],
+ 'Asia/Kolkata': [1970, 1, "M01", 1, 5, 30, 0, 0, 0, 0],
+ 'Asia/Tokyo': [1970, 1, "M01", 1, 9, 0, 0, 0, 0, 0],
+});
+
+// Just before epoch
+test(-1n, {
+ 'America/Los_Angeles': [1969, 12, "M12", 31, 15, 59, 59, 999, 999, 999],
+ 'America/New_York': [1969, 12, "M12", 31, 18, 59, 59, 999, 999, 999],
+ 'Africa/Monrovia': [1969, 12, "M12", 31, 23, 15, 29, 999, 999, 999],
+ 'Europe/London': [1970, 1, "M01", 1, 0, 59, 59, 999, 999, 999],
+ 'Europe/Berlin': [1970, 1, "M01", 1, 0, 59, 59, 999, 999, 999],
+ 'Europe/Moscow': [1970, 1, "M01", 1, 2, 59, 59, 999, 999, 999],
+ 'Asia/Kolkata': [1970, 1, "M01", 1, 5, 29, 59, 999, 999, 999],
+ 'Asia/Tokyo': [1970, 1, "M01", 1, 8, 59, 59, 999, 999, 999],
+});
+
+// Just after epoch
+test(1n, {
+ 'America/Los_Angeles': [1969, 12, "M12", 31, 16, 0, 0, 0, 0, 1],
+ 'America/New_York': [1969, 12, "M12", 31, 19, 0, 0, 0, 0, 1],
+ 'Africa/Monrovia': [1969, 12, "M12", 31, 23, 15, 30, 0, 0, 1],
+ 'Europe/London': [1970, 1, "M01", 1, 1, 0, 0, 0, 0, 1],
+ 'Europe/Berlin': [1970, 1, "M01", 1, 1, 0, 0, 0, 0, 1],
+ 'Europe/Moscow': [1970, 1, "M01", 1, 3, 0, 0, 0, 0, 1],
+ 'Asia/Kolkata': [1970, 1, "M01", 1, 5, 30, 0, 0, 0, 1],
+ 'Asia/Tokyo': [1970, 1, "M01", 1, 9, 0, 0, 0, 0, 1],
+});
+
+// Hours before epoch
+test(-6300_000_000_001n, {
+ 'America/Los_Angeles': [1969, 12, "M12", 31, 14, 14, 59, 999, 999, 999],
+ 'America/New_York': [1969, 12, "M12", 31, 17, 14, 59, 999, 999, 999],
+ 'Africa/Monrovia': [1969, 12, "M12", 31, 21, 30, 29, 999, 999, 999],
+ 'Europe/London': [1969, 12, "M12", 31, 23, 14, 59, 999, 999, 999],
+ 'Europe/Berlin': [1969, 12, "M12", 31, 23, 14, 59, 999, 999, 999],
+ 'Europe/Moscow': [1970, 1, "M01", 1, 1, 14, 59, 999, 999, 999],
+ 'Asia/Kolkata': [1970, 1, "M01", 1, 3, 44, 59, 999, 999, 999],
+ 'Asia/Tokyo': [1970, 1, "M01", 1, 7, 14, 59, 999, 999, 999],
+});
+
+// Hours after epoch
+test(6300_000_000_001n, {
+ 'America/Los_Angeles': [1969, 12, "M12", 31, 17, 45, 0, 0, 0, 1],
+ 'America/New_York': [1969, 12, "M12", 31, 20, 45, 0, 0, 0, 1],
+ 'Africa/Monrovia': [1970, 1, "M01", 1, 1, 0, 30, 0, 0, 1],
+ 'Europe/London': [1970, 1, "M01", 1, 2, 45, 0, 0, 0, 1],
+ 'Europe/Berlin': [1970, 1, "M01", 1, 2, 45, 0, 0, 0, 1],
+ 'Europe/Moscow': [1970, 1, "M01", 1, 4, 45, 0, 0, 0, 1],
+ 'Asia/Kolkata': [1970, 1, "M01", 1, 7, 15, 0, 0, 0, 1],
+ 'Asia/Tokyo': [1970, 1, "M01", 1, 10, 45, 0, 0, 0, 1],
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/dst.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/dst.js
new file mode 100644
index 0000000000..4fb19513d4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/dst.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getplaindatetimefor
+description: Sample of results for IANA time zones around DST changes
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+function test(epochNs, id, expected) {
+ const instant = new Temporal.Instant(epochNs);
+ const tz = new Temporal.TimeZone(id);
+ const dt = tz.getPlainDateTimeFor(instant);
+ TemporalHelpers.assertPlainDateTime(dt, ...expected, `Local time of ${instant} in ${id}`);
+}
+
+// Just before DST forward shift
+test(1553993999_999_999_999n, "Europe/London", [2019, 3, "M03", 31, 0, 59, 59, 999, 999, 999]);
+// Just after DST forward shift
+test(1553994000_000_000_000n, "Europe/London", [2019, 3, "M03", 31, 2, 0, 0, 0, 0, 0]);
+// Just before DST backward shift
+test(1550368799_999_999_999n, "America/Sao_Paulo", [2019, 2, "M02", 16, 23, 59, 59, 999, 999, 999]);
+// Just after DST backward shift
+test(1550368800_000_000_000n, "America/Sao_Paulo", [2019, 2, "M02", 16, 23, 0, 0, 0, 0, 0]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/instant-string.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/instant-string.js
new file mode 100644
index 0000000000..57cd6423c2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/instant-string.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getplaindatetimefor
+description: Conversion of ISO date-time strings to Temporal.Instant instances
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("America/Vancouver");
+
+let str = "1970-01-01T00:00";
+assert.throws(RangeError, () => instance.getPlainDateTimeFor(str), "bare date-time string is not an instant");
+str = "1970-01-01T00:00[America/Vancouver]";
+assert.throws(RangeError, () => instance.getPlainDateTimeFor(str), "date-time + IANA annotation is not an instant");
+
+str = "1970-01-01T00:00Z";
+const result1 = instance.getPlainDateTimeFor(str);
+TemporalHelpers.assertPlainDateTime(result1, 1969, 12, "M12", 31, 16, 0, 0, 0, 0, 0, "date-time + Z preserves exact time");
+
+str = "1970-01-01T00:00+01:00";
+const result2 = instance.getPlainDateTimeFor(str);
+TemporalHelpers.assertPlainDateTime(result2, 1969, 12, "M12", 31, 15, 0, 0, 0, 0, 0, "date-time + offset preserves exact time with offset");
+
+str = "1970-01-01T00:00Z[America/Vancouver]";
+const result3 = instance.getPlainDateTimeFor(str);
+TemporalHelpers.assertPlainDateTime(result3, 1969, 12, "M12", 31, 16, 0, 0, 0, 0, 0, "date-time + Z + IANA annotation ignores the IANA annotation");
+
+str = "1970-01-01T00:00+01:00[America/Vancouver]";
+const result4 = instance.getPlainDateTimeFor(str);
+TemporalHelpers.assertPlainDateTime(result4, 1969, 12, "M12", 31, 15, 0, 0, 0, 0, 0, "date-time + offset + IANA annotation ignores the IANA annotation");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..017b61be76
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.timezone.prototype.getpossibleinstantsfor
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.TimeZone("UTC");
+const base = { era: "ad", month: 5, day: 2, hour: 15, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.getPossibleInstantsFor({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.getPossibleInstantsFor({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPossibleInstantsFor/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/browser.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/nanoseconds-subtracted-or-added-at-dst-transition.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/nanoseconds-subtracted-or-added-at-dst-transition.js
new file mode 100644
index 0000000000..1e978d0936
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/nanoseconds-subtracted-or-added-at-dst-transition.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getprevioustransition
+description: >
+ Test previous transition when nanoseconds are subtracted resp. added to the DST transition.
+features: [Temporal]
+---*/
+
+let tz = new Temporal.TimeZone("Europe/Berlin");
+let p = Temporal.Instant.from("2021-03-28T01:00:00Z");
+
+assert.sameValue(tz.getPreviousTransition(p.add({nanoseconds: -1})).toString(),
+ "2020-10-25T01:00:00Z",
+ "DST transition minus one nanosecond");
+
+assert.sameValue(tz.getPreviousTransition(p).toString(),
+ "2020-10-25T01:00:00Z",
+ "DST transition");
+
+assert.sameValue(tz.getPreviousTransition(p.add({nanoseconds: +1})).toString(),
+ "2021-03-28T01:00:00Z",
+ "DST transition plus one nanosecond");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js
new file mode 100644
index 0000000000..21c4da725b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone.prototype.getprevioustransition
+description: >
+ Test transitions at the instant boundaries.
+features: [Temporal, Intl-enumeration]
+---*/
+
+const min = new Temporal.Instant(-86_40000_00000_00000_00000n);
+const max = new Temporal.Instant(86_40000_00000_00000_00000n);
+
+for (let id of Intl.supportedValuesOf("timeZone")) {
+ let tz = new Temporal.TimeZone(id);
+
+ // If there's any previous transition, it should be before |max|.
+ let prev = tz.getPreviousTransition(max);
+ if (prev) {
+ assert(prev.epochNanoseconds < max.epochNanoseconds);
+ }
+
+ // There shouldn't be any previous transition before |min|.
+ prev = tz.getPreviousTransition(min);
+ assert.sameValue(prev, null);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/shell.js b/js/src/tests/test262/intl402/Temporal/TimeZone/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/supported-values-of.js b/js/src/tests/test262/intl402/Temporal/TimeZone/supported-values-of.js
new file mode 100644
index 0000000000..c8ea68cfa4
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/supported-values-of.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: >
+ TimeZone constructor accepts all time zone identifiers from Intl.supportedValuesOf.
+features: [Temporal, Intl-enumeration]
+---*/
+
+// Ensure all identifiers are valid and canonical.
+for (let id of Intl.supportedValuesOf("timeZone")) {
+ let tz = new Temporal.TimeZone(id);
+
+ assert.sameValue(tz.id, id);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/TimeZone/timezone-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/TimeZone/timezone-case-insensitive.js
new file mode 100644
index 0000000000..bf0b77caf7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/TimeZone/timezone-case-insensitive.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.timezone
+description: Time zone names are case insensitive
+features: [Temporal]
+---*/
+
+const timeZone = 'eTc/gMt+1';
+const result = new Temporal.TimeZone(timeZone);
+assert.sameValue(result.toString(), 'Etc/GMT+1', `Time zone created from string "${timeZone}"`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..48ce8f47d9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.compare
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1588402800_000_000_000n, "America/Vancouver")
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.ZonedDateTime.compare({ year: 2020, month: 5, day: 2, timeZone }, instance);
+assert.sameValue(result1, 0, "date-time + IANA annotation is the IANA time zone (first argument)");
+const result2 = Temporal.ZonedDateTime.compare(instance, { year: 2020, month: 5, day: 2, timeZone });
+assert.sameValue(result1, 0, "date-time + IANA annotation is the IANA time zone (second argument)");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result3 = Temporal.ZonedDateTime.compare({ year: 2020, month: 5, day: 2, timeZone }, instance);
+assert.sameValue(result3, 0, "date-time + Z + IANA annotation is the IANA time zone (first argument)");
+const result4 = Temporal.ZonedDateTime.compare(instance, { year: 2020, month: 5, day: 2, timeZone });
+assert.sameValue(result4, 0, "date-time + Z + IANA annotation is the IANA time zone (second argument)");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result5 = Temporal.ZonedDateTime.compare({ year: 2020, month: 5, day: 2, timeZone }, instance);
+assert.sameValue(result5, 0, "date-time + offset + IANA annotation is the IANA time zone (first argument)");
+const result6 = Temporal.ZonedDateTime.compare(instance, { year: 2020, month: 5, day: 2, timeZone });
+assert.sameValue(result6, 0, "date-time + offset + IANA annotation is the IANA time zone (second argument)");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..bf4aab9601
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/infinity-throws-rangeerror.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if any value in a property bag for either argument is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.compare
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const other = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare({ ...base, eraYear: inf }, other), `eraYear property cannot be ${inf}`);
+
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(other, { ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls1 = [];
+ const obj1 = TemporalHelpers.toPrimitiveObserver(calls1, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare({ ...base, eraYear: obj1 }, other));
+ assert.compareArray(calls1, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+
+ const calls2 = [];
+ const obj2 = TemporalHelpers.toPrimitiveObserver(calls2, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.compare(other, { ...base, eraYear: obj2 }));
+ assert.compareArray(calls2, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/compare/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..810853124a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+const result1 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result1.timeZoneId, "America/Vancouver", "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+const result2 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result2.timeZoneId, "America/Vancouver", "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+const result3 = Temporal.ZonedDateTime.from({ year: 2000, month: 5, day: 2, timeZone });
+assert.sameValue(result3.timeZoneId, "America/Vancouver", "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/do-not-canonicalize-iana-identifiers.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/do-not-canonicalize-iana-identifiers.js
new file mode 100644
index 0000000000..6e894c0f26
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/do-not-canonicalize-iana-identifiers.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: ZonedDateTime.from does not canonicalize time zone IDs
+features: [Temporal]
+---*/
+
+const calcutta = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+const kolkata = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Kolkata]');
+
+assert.sameValue(calcutta.toString(), '2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+assert.sameValue(calcutta.toJSON(), '2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+assert.sameValue(calcutta.timeZoneId, 'Asia/Calcutta');
+
+assert.sameValue(kolkata.toString(), '2020-01-01T00:00:00+05:30[Asia/Kolkata]');
+assert.sameValue(kolkata.toJSON(), '2020-01-01T00:00:00+05:30[Asia/Kolkata]');
+assert.sameValue(kolkata.timeZoneId, 'Asia/Kolkata');
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..61e399957a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/infinity-throws-rangeerror.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.from
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ ["constrain", "reject"].forEach((overflow) => {
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.from({ ...base, eraYear: inf }, { overflow }), `eraYear property cannot be ${inf} (overflow ${overflow}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => Temporal.ZonedDateTime.from({ ...base, eraYear: obj }, { overflow }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js
new file mode 100644
index 0000000000..092e5e0be7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js
@@ -0,0 +1,630 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: Time zone identifiers are case-normalized
+features: [Temporal]
+---*/
+
+const timeZoneIdentifiers = [
+ // IANA TZDB Zone names
+ 'Africa/Abidjan',
+ 'Africa/Algiers',
+ 'Africa/Bissau',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/El_Aaiun',
+ 'Africa/Johannesburg',
+ 'Africa/Juba',
+ 'Africa/Khartoum',
+ 'Africa/Lagos',
+ 'Africa/Maputo',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Sao_Tome',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Asuncion',
+ 'America/Bahia',
+ 'America/Bahia_Banderas',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Cayenne',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ // 'America/Ciudad_Juarez' // uncomment after Node supports this ID added in TZDB 2022g
+ 'America/Costa_Rica',
+ 'America/Cuiaba',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Fort_Nelson',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Metlakatla',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/New_York',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Beulah',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Nuuk',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port-au-Prince',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Punta_Arenas',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/Sitka',
+ 'America/St_Johns',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Vancouver',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/Macquarie',
+ 'Antarctica/Mawson',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/Troll',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Atyrau',
+ 'Asia/Baghdad',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Barnaul',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Chita',
+ 'Asia/Choibalsan',
+ 'Asia/Colombo',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Famagusta',
+ 'Asia/Gaza',
+ 'Asia/Hebron',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kathmandu',
+ 'Asia/Khandyga',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuching',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qostanay',
+ 'Asia/Qyzylorda',
+ 'Asia/Riyadh',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Srednekolymsk',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Tomsk',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Urumqi',
+ 'Asia/Ust-Nera',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yangon',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faroe',
+ 'Atlantic/Madeira',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/Stanley',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/Perth',
+ 'Australia/Sydney',
+ 'CET',
+ 'CST6CDT',
+ 'EET',
+ 'EST',
+ 'EST5EDT',
+ 'Etc/GMT',
+ 'Etc/GMT+1',
+ 'Etc/GMT+10',
+ 'Etc/GMT+11',
+ 'Etc/GMT+12',
+ 'Etc/GMT+2',
+ 'Etc/GMT+3',
+ 'Etc/GMT+4',
+ 'Etc/GMT+5',
+ 'Etc/GMT+6',
+ 'Etc/GMT+7',
+ 'Etc/GMT+8',
+ 'Etc/GMT+9',
+ 'Etc/GMT-1',
+ 'Etc/GMT-10',
+ 'Etc/GMT-11',
+ 'Etc/GMT-12',
+ 'Etc/GMT-13',
+ 'Etc/GMT-14',
+ 'Etc/GMT-2',
+ 'Etc/GMT-3',
+ 'Etc/GMT-4',
+ 'Etc/GMT-5',
+ 'Etc/GMT-6',
+ 'Etc/GMT-7',
+ 'Etc/GMT-8',
+ 'Etc/GMT-9',
+ 'Etc/UTC',
+ 'Europe/Andorra',
+ 'Europe/Astrakhan',
+ 'Europe/Athens',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Chisinau',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Helsinki',
+ 'Europe/Istanbul',
+ 'Europe/Kaliningrad',
+ 'Europe/Kirov',
+ 'Europe/Kyiv',
+ 'Europe/Lisbon',
+ 'Europe/London',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Minsk',
+ 'Europe/Moscow',
+ 'Europe/Paris',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/Saratov',
+ 'Europe/Simferopol',
+ 'Europe/Sofia',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Ulyanovsk',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zurich',
+ 'HST',
+ 'Indian/Chagos',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'MET',
+ 'MST',
+ 'MST7MDT',
+ 'PST8PDT',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Bougainville',
+ 'Pacific/Chatham',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Kanton',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Marquesas',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+ 'WET',
+
+ // IANA TZDB Link names
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Kampala',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Timbuktu',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Argentina/ComodRivadavia',
+ 'America/Aruba',
+ 'America/Atikokan',
+ 'America/Atka',
+ 'America/Blanc-Sablon',
+ 'America/Buenos_Aires',
+ 'America/Catamarca',
+ 'America/Cayman',
+ 'America/Coral_Harbour',
+ 'America/Cordoba',
+ 'America/Creston',
+ 'America/Curacao',
+ 'America/Dominica',
+ 'America/Ensenada',
+ 'America/Fort_Wayne',
+ 'America/Godthab',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Indianapolis',
+ 'America/Jujuy',
+ 'America/Knox_IN',
+ 'America/Kralendijk',
+ 'America/Louisville',
+ 'America/Lower_Princes',
+ 'America/Marigot',
+ 'America/Mendoza',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/Nipigon',
+ 'America/Pangnirtung',
+ 'America/Port_of_Spain',
+ 'America/Porto_Acre',
+ 'America/Rainy_River',
+ 'America/Rosario',
+ 'America/Santa_Isabel',
+ 'America/Shiprock',
+ 'America/St_Barthelemy',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Thunder_Bay',
+ 'America/Tortola',
+ 'America/Virgin',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/McMurdo',
+ 'Antarctica/South_Pole',
+ 'Antarctica/Syowa',
+ 'Antarctica/Vostok',
+ 'Arctic/Longyearbyen',
+ 'Asia/Aden',
+ 'Asia/Ashkhabad',
+ 'Asia/Bahrain',
+ 'Asia/Brunei',
+ 'Asia/Calcutta',
+ 'Asia/Chongqing',
+ 'Asia/Chungking',
+ 'Asia/Dacca',
+ 'Asia/Harbin',
+ 'Asia/Istanbul',
+ 'Asia/Kashgar',
+ 'Asia/Katmandu',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuwait',
+ 'Asia/Macao',
+ 'Asia/Muscat',
+ 'Asia/Phnom_Penh',
+ 'Asia/Rangoon',
+ 'Asia/Saigon',
+ 'Asia/Tel_Aviv',
+ 'Asia/Thimbu',
+ 'Asia/Ujung_Pandang',
+ 'Asia/Ulan_Bator',
+ 'Asia/Vientiane',
+ 'Atlantic/Faeroe',
+ 'Atlantic/Jan_Mayen',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/St_Helena',
+ 'Australia/ACT',
+ 'Australia/Canberra',
+ 'Australia/Currie',
+ 'Australia/LHI',
+ 'Australia/NSW',
+ 'Australia/North',
+ 'Australia/Queensland',
+ 'Australia/South',
+ 'Australia/Tasmania',
+ 'Australia/Victoria',
+ 'Australia/West',
+ 'Australia/Yancowinna',
+ 'Brazil/Acre',
+ 'Brazil/DeNoronha',
+ 'Brazil/East',
+ 'Brazil/West',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Canada/Saskatchewan',
+ 'Canada/Yukon',
+ 'Chile/Continental',
+ 'Chile/EasterIsland',
+ 'Cuba',
+ 'Egypt',
+ 'Eire',
+ 'Etc/GMT+0',
+ 'Etc/GMT-0',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'Etc/UCT',
+ 'Etc/Universal',
+ 'Etc/Zulu',
+ 'Europe/Amsterdam',
+ 'Europe/Belfast',
+ 'Europe/Bratislava',
+ 'Europe/Busingen',
+ 'Europe/Copenhagen',
+ 'Europe/Guernsey',
+ 'Europe/Isle_of_Man',
+ 'Europe/Jersey',
+ 'Europe/Kiev',
+ 'Europe/Ljubljana',
+ 'Europe/Luxembourg',
+ 'Europe/Mariehamn',
+ 'Europe/Monaco',
+ 'Europe/Nicosia',
+ 'Europe/Oslo',
+ 'Europe/Podgorica',
+ 'Europe/San_Marino',
+ 'Europe/Sarajevo',
+ 'Europe/Skopje',
+ 'Europe/Stockholm',
+ 'Europe/Tiraspol',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vatican',
+ 'Europe/Zagreb',
+ 'Europe/Zaporozhye',
+ 'GB',
+ 'GB-Eire',
+ 'GMT',
+ 'GMT+0',
+ 'GMT-0',
+ 'GMT0',
+ 'Greenwich',
+ 'Hongkong',
+ 'Iceland',
+ 'Indian/Antananarivo',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Iran',
+ 'Israel',
+ 'Jamaica',
+ 'Japan',
+ 'Kwajalein',
+ 'Libya',
+ 'Mexico/BajaNorte',
+ 'Mexico/BajaSur',
+ 'Mexico/General',
+ 'NZ',
+ 'NZ-CHAT',
+ 'Navajo',
+ 'PRC',
+ 'Pacific/Chuuk',
+ 'Pacific/Enderbury',
+ 'Pacific/Funafuti',
+ 'Pacific/Johnston',
+ 'Pacific/Majuro',
+ 'Pacific/Midway',
+ 'Pacific/Pohnpei',
+ 'Pacific/Ponape',
+ 'Pacific/Saipan',
+ 'Pacific/Samoa',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'Pacific/Yap',
+ 'Poland',
+ 'Portugal',
+ 'ROC',
+ 'ROK',
+ 'Singapore',
+ 'Turkey',
+ 'UCT',
+ 'US/Alaska',
+ 'US/Aleutian',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/East-Indiana',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Indiana-Starke',
+ 'US/Michigan',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'US/Pacific-New',
+ 'US/Samoa',
+ 'UTC',
+ 'Universal',
+ 'W-SU',
+ 'Zulu'
+];
+
+// We want to test all available named time zone identifiers (both primary and non-primary),
+// but no ECMAScript built-in API exposes that list. So we use a union of two sources:
+// 1. A hard-coded list of Zone and Link identifiers from the 2022g version of IANA TZDB.
+// 2. Canonical IDs exposed by Intl.supportedValuesOf('timeZone'), which ensures that IDs
+// added to TZDB later than 2022g will be tested. (New IDs are almost always added as primary.)
+const ids = [...new Set([...timeZoneIdentifiers, ...Intl.supportedValuesOf('timeZone')])];
+for (const id of ids) {
+ const lower = id.toLowerCase();
+ const upper = id.toUpperCase();
+ for (const idToTest of [id, lower, upper]) {
+ const isoString = `2020-01-01[${idToTest}]`;
+ const zdt = Temporal.ZonedDateTime.from(isoString);
+ assert.sameValue(zdt.timeZoneId, id, `Time zone created from string "${isoString}"`);
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/zoneddatetime-sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/zoneddatetime-sub-minute-offset.js
new file mode 100644
index 0000000000..70dd918151
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/from/zoneddatetime-sub-minute-offset.js
@@ -0,0 +1,106 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.from
+description: Fuzzy matching behaviour with UTC offsets in ISO 8601 strings with named time zones and offset option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+["use", "ignore", "prefer", "reject"].forEach((offset) => {
+ const result = Temporal.ZonedDateTime.from("1970-01-01T12:00-00:44:30[Africa/Monrovia]", { offset });
+ assert.sameValue(result.epochNanoseconds, 45870_000_000_000n, `accepts the exact offset string (offset: ${offset})`);
+ assert.sameValue(result.offset, "-00:44:30", "offset property is correct");
+});
+
+["use", "ignore", "prefer", "reject"].forEach((offset) => {
+ const result = Temporal.ZonedDateTime.from("1970-01-01T12:00-00:44:30.000000000[Africa/Monrovia]", { offset });
+ assert.sameValue(
+ result.epochNanoseconds,
+ 45870_000_000_000n,
+ `accepts trailing zeroes after ISO string offset (offset: ${offset})`
+ );
+ assert.sameValue(result.offset, "-00:44:30", "offset property removes trailing zeroes from input");
+});
+
+assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.from("1970-01-01T00:00-00:44:30[-00:45]", { offset: "reject" }),
+ "minute rounding not supported for offset time zones"
+);
+
+const str = "1970-01-01T12:00-00:45[Africa/Monrovia]";
+
+["ignore", "prefer", "reject"].forEach((offset) => {
+ const result = Temporal.ZonedDateTime.from(str, { offset });
+ assert.sameValue(
+ result.epochNanoseconds,
+ 45870_000_000_000n,
+ `accepts the offset string rounded to minutes (offset=${offset})`
+ );
+ assert.sameValue(result.offset, "-00:44:30", "offset property is still the full precision");
+ TemporalHelpers.assertPlainDateTime(
+ result.toPlainDateTime(),
+ 1970,
+ 1,
+ "M01",
+ 1,
+ 12,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ "wall time is preserved"
+ );
+});
+
+const result = Temporal.ZonedDateTime.from(str, { offset: "use" });
+assert.sameValue(
+ result.epochNanoseconds,
+ 45900_000_000_000n,
+ "prioritizes the offset string with HH:MM precision when offset=use"
+);
+assert.sameValue(result.offset, "-00:44:30", "offset property is still the full precision");
+TemporalHelpers.assertPlainDateTime(
+ result.toPlainDateTime(),
+ 1970,
+ 1,
+ "M01",
+ 1,
+ 12,
+ 0,
+ 30,
+ 0,
+ 0,
+ 0,
+ "wall time is shifted by the difference between exact and rounded offset"
+);
+
+const properties = { year: 1970, month: 1, day: 1, hour: 12, offset: "-00:45", timeZone: "Africa/Monrovia" };
+
+["ignore", "prefer"].forEach((offset) => {
+ const result = Temporal.ZonedDateTime.from(properties, { offset });
+ assert.sameValue(
+ result.epochNanoseconds,
+ 45870_000_000_000n,
+ `no fuzzy matching is done on offset in property bag (offset=${offset})`
+ );
+});
+
+const result2 = Temporal.ZonedDateTime.from(properties, { offset: "use" });
+assert.sameValue(
+ result2.epochNanoseconds,
+ 45900_000_000_000n,
+ "no fuzzy matching is done on offset in property bag (offset=use)"
+);
+
+assert.throws(
+ RangeError,
+ () => Temporal.ZonedDateTime.from(properties, { offset: "reject" }),
+ "no fuzzy matching is done on offset in property bag (offset=reject)"
+);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..c88ed0455e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + Z + IANA annotation is the IANA time zone");
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+assert(instance.equals({ year: 1969, month: 12, day: 31, hour: 16, timeZone }), "date-time + offset + IANA annotation is the IANA time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/canonicalize-iana-identifiers-before-comparing.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/canonicalize-iana-identifiers-before-comparing.js
new file mode 100644
index 0000000000..1139d39f4e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/canonicalize-iana-identifiers-before-comparing.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: ZonedDateTime.p.equals canonicalizes time zone IDs before comparing them
+features: [Temporal]
+---*/
+
+const calcutta = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+const kolkata = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Kolkata]');
+const colombo = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Colombo]');
+
+assert.sameValue(calcutta.equals(kolkata), true);
+assert.sameValue(calcutta.equals(kolkata.toString()), true);
+assert.sameValue(kolkata.equals(calcutta), true);
+assert.sameValue(kolkata.equals(calcutta.toString()), true);
+assert.sameValue(calcutta.equals(colombo), false);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/custom-time-zone-ids-case-sensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/custom-time-zone-ids-case-sensitive.js
new file mode 100644
index 0000000000..1a1fe83615
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/custom-time-zone-ids-case-sensitive.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: Custom time zone IDs are compared case-sensitively
+features: [Temporal]
+---*/
+
+class Custom extends Temporal.TimeZone {
+ constructor(id) {
+ super("UTC");
+ this._id = id;
+ }
+ get id() {
+ return this._id;
+ }
+}
+const custom = Temporal.ZonedDateTime.from({ year: 2020, month: 1, day: 1, timeZone: new Custom("Moon/Cheese") });
+const customSameCase = custom.withTimeZone(new Custom("Moon/Cheese"));
+const customDifferentCase = custom.withTimeZone(new Custom("MOON/CHEESE"));
+
+assert.sameValue(custom.equals(customSameCase), true);
+assert.sameValue(custom.equals(customDifferentCase), false);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..0a8b2aca0e
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.equals
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.equals({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/sub-minute-offset.js
new file mode 100644
index 0000000000..da6aef1883
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/equals/sub-minute-offset.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.equals
+description: Fuzzy matching behaviour for UTC offset in ISO 8601 string with named time zones
+features: [Temporal]
+---*/
+
+const expectedNanoseconds = BigInt((44 * 60 + 30) * 1e9);
+const timeZone = new Temporal.TimeZone("Africa/Monrovia");
+const instance = new Temporal.ZonedDateTime(expectedNanoseconds, timeZone);
+
+let result = instance.equals("1970-01-01T00:00:00-00:45[Africa/Monrovia]");
+assert.sameValue(result, true, "UTC offset rounded to minutes is accepted");
+
+result = instance.equals("1970-01-01T00:00:00-00:44:30[Africa/Monrovia]");
+assert.sameValue(result, true, "Unrounded sub-minute UTC offset also accepted");
+
+assert.throws(
+ RangeError,
+ () => instance.equals("1970-01-01T00:00:00-00:44:30[-00:45]"),
+ "minute rounding not supported for offset time zones"
+);
+
+const properties = {
+ offset: "-00:45",
+ year: 1970,
+ month: 1,
+ day: 1,
+ minute: 44,
+ second: 30,
+ timeZone
+};
+assert.throws(RangeError, () => instance.equals(properties), "no fuzzy matching is done on offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js
new file mode 100644
index 0000000000..84f10f82b9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const era = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "era").get;
+
+assert.sameValue(typeof era, "function");
+
+assert.throws(TypeError, () => era.call(undefined), "undefined");
+assert.throws(TypeError, () => era.call(null), "null");
+assert.throws(TypeError, () => era.call(true), "true");
+assert.throws(TypeError, () => era.call(""), "empty string");
+assert.throws(TypeError, () => era.call(Symbol()), "symbol");
+assert.throws(TypeError, () => era.call(1), "1");
+assert.throws(TypeError, () => era.call({}), "plain object");
+assert.throws(TypeError, () => era.call(Temporal.ZonedDateTime), "Temporal.ZonedDateTime");
+assert.throws(TypeError, () => era.call(Temporal.ZonedDateTime.prototype), "Temporal.ZonedDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js
new file mode 100644
index 0000000000..d247224b40
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: The "era" property of Temporal.ZonedDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "era");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..222be0b287
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..47c31fdd68
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => datetime.era,
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..2544695b31
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..300233e3d6
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => datetime.era);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js
new file mode 100644
index 0000000000..13f0bc5e8c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/era/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.era
+description: Validate result returned from calendar era() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, TypeError],
+ [-Infinity, TypeError],
+ [NaN, TypeError],
+ [-7, TypeError],
+ [-0.1, TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, TypeError],
+ [{valueOf() { return "7"; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.throws(error, () => instance.era, `${typeof result} ${String(result)} not converted to string`);
+});
+
+const preservedResults = [
+ undefined,
+ "string",
+ "7",
+ "7.5",
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ era() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.sameValue(instance.era, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js
new file mode 100644
index 0000000000..483656efd9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/branding.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: Throw a TypeError if the receiver is invalid
+features: [Symbol, Temporal]
+---*/
+
+const eraYear = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "eraYear").get;
+
+assert.sameValue(typeof eraYear, "function");
+
+assert.throws(TypeError, () => eraYear.call(undefined), "undefined");
+assert.throws(TypeError, () => eraYear.call(null), "null");
+assert.throws(TypeError, () => eraYear.call(true), "true");
+assert.throws(TypeError, () => eraYear.call(""), "empty string");
+assert.throws(TypeError, () => eraYear.call(Symbol()), "symbol");
+assert.throws(TypeError, () => eraYear.call(1), "1");
+assert.throws(TypeError, () => eraYear.call({}), "plain object");
+assert.throws(TypeError, () => eraYear.call(Temporal.ZonedDateTime), "Temporal.ZonedDateTime");
+assert.throws(TypeError, () => eraYear.call(Temporal.ZonedDateTime.prototype), "Temporal.ZonedDateTime.prototype");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js
new file mode 100644
index 0000000000..92f5485722
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/prop-desc.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: The "eraYear" property of Temporal.ZonedDateTime.prototype
+features: [Temporal]
+---*/
+
+const descriptor = Object.getOwnPropertyDescriptor(Temporal.ZonedDateTime.prototype, "eraYear");
+assert.sameValue(typeof descriptor.get, "function");
+assert.sameValue(descriptor.set, undefined);
+assert.sameValue(descriptor.enumerable, false);
+assert.sameValue(descriptor.configurable, true);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js
new file mode 100644
index 0000000000..ca170de242
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-non-integer.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is not an integer number of nanoseconds
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[3600_000_000_000.5, NaN, -Infinity, Infinity].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js
new file mode 100644
index 0000000000..6631a41232
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-not-callable.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: TypeError thrown if timeZone.getOffsetNanosecondsFor is not callable
+features: [BigInt, Symbol, Temporal, arrow-function]
+---*/
+
+[undefined, null, true, Math.PI, 'string', Symbol('sym'), 42n, {}].forEach((notCallable) => {
+ const timeZone = new Temporal.TimeZone("UTC");
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ timeZone.getOffsetNanosecondsFor = notCallable;
+ assert.throws(
+ TypeError,
+ () => datetime.eraYear,
+ `Uncallable ${notCallable === null ? 'null' : typeof notCallable} getOffsetNanosecondsFor should throw TypeError`
+ );
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js
new file mode 100644
index 0000000000..00a6903d20
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-out-of-range.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: RangeError thrown if time zone reports an offset that is out of range
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[-86400_000_000_000, 86400_000_000_000].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(RangeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js
new file mode 100644
index 0000000000..c743b5d188
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/timezone-getoffsetnanosecondsfor-wrong-type.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: TypeError thrown if time zone reports an offset that is not a Number
+features: [Temporal]
+includes: [temporalHelpers.js]
+---*/
+
+[
+ undefined,
+ null,
+ true,
+ "+01:00",
+ Symbol(),
+ 3600_000_000_000n,
+ {},
+ { valueOf() { return 3600_000_000_000; } },
+].forEach((wrongOffset) => {
+ const timeZone = TemporalHelpers.specificOffsetTimeZone(wrongOffset);
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, timeZone);
+ assert.throws(TypeError, () => datetime.eraYear);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js
new file mode 100644
index 0000000000..6b83d83c73
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/eraYear/validate-calendar-value.js
@@ -0,0 +1,54 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Richard Gibson. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-get-temporal.zoneddatetime.prototype.erayear
+description: Validate result returned from calendar eraYear() method
+features: [Temporal]
+---*/
+
+const badResults = [
+ [null, TypeError],
+ [false, TypeError],
+ [Infinity, RangeError],
+ [-Infinity, RangeError],
+ [NaN, RangeError],
+ [-0.1, RangeError],
+ ["string", TypeError],
+ [Symbol("foo"), TypeError],
+ [7n, TypeError],
+ [{}, TypeError],
+ [true, TypeError],
+ [7.1, RangeError],
+ ["7", TypeError],
+ ["7.5", TypeError],
+ [{valueOf() { return 7; }}, TypeError],
+];
+
+badResults.forEach(([result, error]) => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.throws(error, () => instance.eraYear, `${typeof result} ${String(result)} not converted to integer`);
+});
+
+const preservedResults = [
+ undefined,
+ -7,
+];
+
+preservedResults.forEach(result => {
+ const calendar = new class extends Temporal.Calendar {
+ eraYear() {
+ return result;
+ }
+ }("iso8601");
+ const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+ assert.sameValue(instance.eraYear, result, `${typeof result} ${String(result)} preserved`);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..b867e125fd
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+instance.since({ year: 2020, month: 5, day: 2, timeZone });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/canonicalize-iana-identifiers-before-comparing.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/canonicalize-iana-identifiers-before-comparing.js
new file mode 100644
index 0000000000..3523b994d5
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/canonicalize-iana-identifiers-before-comparing.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Accept time zone identifiers that canonicalize to the same ID
+features: [Temporal]
+---*/
+
+const calcutta = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+const kolkata = Temporal.ZonedDateTime.from('2021-09-01T00:00:00+05:30[Asia/Kolkata]');
+const colombo = Temporal.ZonedDateTime.from('2022-08-01T00:00:00+05:30[Asia/Colombo]');
+
+// If the time zones resolve to the same canonical zone, then it shouldn't throw
+assert.sameValue(calcutta.since(kolkata, { largestUnit: 'day' }).toString(), '-P609D');
+assert.throws(RangeError, () => calcutta.since(colombo, { largestUnit: 'day' }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/custom-time-zone-ids-case-sensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/custom-time-zone-ids-case-sensitive.js
new file mode 100644
index 0000000000..a81fbb4f3a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/custom-time-zone-ids-case-sensitive.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Custom time zone IDs are compared case-sensitively
+features: [Temporal]
+---*/
+
+class Custom extends Temporal.TimeZone {
+ constructor(id) {
+ super("UTC");
+ this._id = id;
+ }
+ get id() {
+ return this._id;
+ }
+}
+const custom = Temporal.ZonedDateTime.from({ year: 2020, month: 1, day: 1, timeZone: new Custom("Moon/Cheese") });
+const customSameCase = custom.withTimeZone(new Custom("Moon/Cheese")).with({ year: 2021 });
+const customDifferentCase = custom.withTimeZone(new Custom("MOON/CHEESE")).with({ year: 2021 });
+
+assert.sameValue(custom.since(customSameCase, { largestUnit: "year" }).toString(), "-P1Y");
+assert.throws(RangeError, () => custom.since(customDifferentCase, { largestUnit: "year" }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..3f0fc2d65f
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.since
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.since({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/sub-minute-offset.js
new file mode 100644
index 0000000000..cc5d25cbcb
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/since/sub-minute-offset.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Fuzzy matching behaviour for UTC offset in ISO 8601 string with named time zones
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("Africa/Monrovia");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+let result = instance.since("1970-01-01T00:44:30-00:44:30[Africa/Monrovia]");
+TemporalHelpers.assertDuration(result, 0, 0, 0, 0, -1, -29, 0, 0, 0, 0, "UTC offset rounded to minutes is accepted");
+
+result = instance.since("1970-01-01T00:44:30-00:44:30[Africa/Monrovia]");
+TemporalHelpers.assertDuration(
+ result,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1,
+ -29,
+ 0,
+ 0,
+ 0,
+ 0,
+ "Unrounded sub-minute UTC offset also accepted"
+);
+
+assert.throws(
+ RangeError,
+ () => instance.since("1970-01-01T00:44:30+00:44:30[+00:45"),
+ "minute rounding not supported for offset time zones"
+);
+
+const properties = {
+ offset: "-00:45",
+ year: 1970,
+ month: 1,
+ day: 1,
+ minute: 44,
+ second: 30,
+ timeZone
+};
+assert.throws(RangeError, () => instance.since(properties), "no fuzzy matching is done on offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js
new file mode 100644
index 0000000000..c34862ddf7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Calendar must match the locale calendar if not "iso8601"
+features: [Temporal, Intl-enumeration]
+---*/
+
+const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
+assert.notSameValue(localeCalendar, "iso8601", "no locale has the ISO calendar");
+
+const sameCalendarInstance = new Temporal.ZonedDateTime(0n, "UTC", localeCalendar);
+const result = sameCalendarInstance.toLocaleString();
+assert.sameValue(typeof result, "string", "toLocaleString() succeeds when instance has the same calendar as locale");
+
+const isoInstance = new Temporal.ZonedDateTime(0n, "UTC", "iso8601");
+assert.sameValue(isoInstance.toLocaleString(), result, "toLocaleString() succeeds when instance has the ISO calendar")
+
+// Pick a different calendar that is not ISO and not the locale's calendar
+const calendars = new Set(Intl.supportedValuesOf("calendar"));
+calendars.delete("iso8601");
+calendars.delete(localeCalendar);
+const differentCalendar = calendars.values().next().value;
+
+const differentCalendarInstance = new Temporal.ZonedDateTime(0n, "UTC", differentCalendar);
+assert.throws(RangeError, () => differentCalendarInstance.toLocaleString(), "calendar mismatch");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/custom-time-zone-name-not-supported.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/custom-time-zone-name-not-supported.js
new file mode 100644
index 0000000000..1e1a0f17b7
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/custom-time-zone-name-not-supported.js
@@ -0,0 +1,20 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: >
+ Custom time zones with unofficial names are not supported for locale formatting
+features: [Temporal]
+---*/
+
+const timeZone = {
+ id: "Etc/Custom_Zone",
+ getPossibleInstantsFor() {},
+ getOffsetNanosecondsFor() {},
+};
+const datetime = new Temporal.ZonedDateTime(0n, timeZone);
+assert.throws(RangeError, () => datetime.toLocaleString(), "Custom time zones with non-IANA identifiers not supported in Intl");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js
new file mode 100644
index 0000000000..8f4f19308a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Omitting the locales argument defaults to the DateTimeFormat default
+features: [BigInt, Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+const defaultFormatter = new Intl.DateTimeFormat([], {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ timeZoneName: "short",
+ timeZone: "UTC",
+});
+const expected = defaultFormatter.format(datetime.toInstant());
+
+const actualExplicit = datetime.toLocaleString(undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString();
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/offset-time-zone-not-supported.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/offset-time-zone-not-supported.js
new file mode 100644
index 0000000000..4522df5ab2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/offset-time-zone-not-supported.js
@@ -0,0 +1,14 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Offset time zones are not supported yet by Intl
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(0n, "+00:00");
+assert.throws(RangeError, () => datetime.toLocaleString(), "Intl.DateTimeFormat does not yet specify what to do with offset time zones");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js
new file mode 100644
index 0000000000..385b3f9f05
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Kate Miháliková. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-temporal.zoneddatetime.prototype.tolocalestring
+description: >
+ Conflicting properties of dateStyle must be rejected with a TypeError for the options argument
+info: |
+ Using sec-temporal-getdatetimeformatpattern:
+ GetDateTimeFormatPattern ( dateStyle, timeStyle, matcher, opt, dataLocaleData, hc )
+
+ 1. If dateStyle is not undefined or timeStyle is not undefined, then
+ a. For each row in Table 7, except the header row, do
+ i. Let prop be the name given in the Property column of the row.
+ ii. Let p be opt.[[<prop>]].
+ iii. If p is not undefined, then
+ 1. Throw a TypeError exception.
+features: [BigInt, Temporal]
+---*/
+
+// Table 14 - Supported fields + example value for each field
+const conflictingOptions = [
+ [ "weekday", "short" ],
+ [ "era", "short" ],
+ [ "year", "numeric" ],
+ [ "month", "numeric" ],
+ [ "day", "numeric" ],
+ [ "hour", "numeric" ],
+ [ "minute", "numeric" ],
+ [ "second", "numeric" ],
+ [ "dayPeriod", "short" ],
+ [ "fractionalSecondDigits", 3 ],
+ [ "timeZoneName", "short" ],
+];
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+
+assert.sameValue(typeof datetime.toLocaleString("en", { dateStyle: "short" }), "string");
+assert.sameValue(typeof datetime.toLocaleString("en", { timeStyle: "short" }), "string");
+
+for (const [ option, value ] of conflictingOptions) {
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, dateStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", dateStyle: "short" }) throws TypeError`);
+
+ assert.throws(TypeError, function() {
+ datetime.toLocaleString("en", { [option]: value, timeStyle: "short" });
+ }, `datetime.toLocaleString("en", { ${option}: "${value}", timeStyle: "short" }) throws TypeError`);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZone.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZone.js
new file mode 100644
index 0000000000..034c485084
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZone.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: >
+ Options must not have a timeZone property, even if it agrees with the
+ instance's time zone
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(0n, "UTC");
+
+assert.throws(TypeError, () => datetime.toLocaleString("en-US", { timeZone: "Europe/Vienna" }), "timeZone option disallowed");
+assert.throws(TypeError, () => datetime.toLocaleString("en-US", { timeZone: "UTC" }), "timeZone option disallowed even if it agrees with instance's time zone");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZoneName-affects-instance-time-zone.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZoneName-affects-instance-time-zone.js
new file mode 100644
index 0000000000..51a5c1daef
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZoneName-affects-instance-time-zone.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: timeZoneName option affects formatting of the instance's time zone
+locale: [en-US]
+features: [Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(0n, "Europe/Vienna");
+
+const resultShort = datetime.toLocaleString("en-US", { timeZoneName: "short" });
+const resultLong = datetime.toLocaleString("en-US", { timeZoneName: "long" });
+assert.notSameValue(resultShort, resultLong, "formats with different timeZoneName options should be different");
+assert(resultLong.includes("Central European Standard Time"), "time zone name can be written out in full");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js
new file mode 100644
index 0000000000..a1286949a9
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Verify that undefined options are handled correctly.
+features: [BigInt, Temporal]
+---*/
+
+const datetime = new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC");
+const defaultFormatter = new Intl.DateTimeFormat('en', {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ timeZoneName: "short",
+ timeZone: "UTC",
+});
+const expected = defaultFormatter.format(datetime.toInstant());
+
+const actualExplicit = datetime.toLocaleString('en', undefined);
+assert.sameValue(actualExplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+const actualImplicit = datetime.toLocaleString('en');
+assert.sameValue(actualImplicit, expected, "default locale is determined by Intl.DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/time-zone-canonicalized.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/time-zone-canonicalized.js
new file mode 100644
index 0000000000..a2c010e248
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/toLocaleString/time-zone-canonicalized.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.tolocalestring
+description: Custom time zone names are canonicalized
+features: [Temporal]
+---*/
+
+const timeZone1 = {
+ id: "Asia/Kolkata",
+ getPossibleInstantsFor() {},
+ getOffsetNanosecondsFor() {},
+};
+const datetime1 = new Temporal.ZonedDateTime(0n, timeZone1);
+
+const timeZone2 = {
+ id: "Asia/Calcutta",
+ getPossibleInstantsFor() {},
+ getOffsetNanosecondsFor() {},
+};
+const datetime2 = new Temporal.ZonedDateTime(0n, timeZone2);
+
+assert.sameValue(datetime1.toLocaleString(), datetime2.toLocaleString(), "Time zone names are canonicalized before passing to DateTimeFormat");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js
new file mode 100644
index 0000000000..c491b7592b
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-string-datetime.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Conversion of ISO date-time strings to Temporal.TimeZone instances (with IANA time zones)
+features: [Temporal]
+---*/
+
+const expectedTimeZone = "America/Vancouver";
+const instance = new Temporal.ZonedDateTime(0n, expectedTimeZone);
+let timeZone = "2021-08-19T17:30[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+
+timeZone = "2021-08-19T17:30Z[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+
+timeZone = "2021-08-19T17:30-07:00[America/Vancouver]";
+instance.until({ year: 2020, month: 5, day: 2, timeZone });
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/canonicalize-iana-identifiers-before-comparing.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/canonicalize-iana-identifiers-before-comparing.js
new file mode 100644
index 0000000000..eb93477e30
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/canonicalize-iana-identifiers-before-comparing.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Accept time zone identifiers that canonicalize to the same ID
+features: [Temporal]
+---*/
+
+const calcutta = Temporal.ZonedDateTime.from('2020-01-01T00:00:00+05:30[Asia/Calcutta]');
+const kolkata = Temporal.ZonedDateTime.from('2021-09-01T00:00:00+05:30[Asia/Kolkata]');
+const colombo = Temporal.ZonedDateTime.from('2022-08-01T00:00:00+05:30[Asia/Colombo]');
+
+// If the time zones resolve to the same canonical zone, then it shouldn't throw
+assert.sameValue(calcutta.until(kolkata, { largestUnit: 'day' }).toString(), 'P609D');
+assert.throws(RangeError, () => calcutta.until(colombo, { largestUnit: 'day' }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/custom-time-zone-ids-case-sensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/custom-time-zone-ids-case-sensitive.js
new file mode 100644
index 0000000000..7c1ad5b356
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/custom-time-zone-ids-case-sensitive.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2023 Justin Grant. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.since
+description: Custom time zone IDs are compared case-sensitively
+features: [Temporal]
+---*/
+
+class Custom extends Temporal.TimeZone {
+ constructor(id) {
+ super("UTC");
+ this._id = id;
+ }
+ get id() {
+ return this._id;
+ }
+}
+const custom = Temporal.ZonedDateTime.from({ year: 2020, month: 1, day: 1, timeZone: new Custom("Moon/Cheese") });
+const customSameCase = custom.withTimeZone(new Custom("Moon/Cheese")).with({ year: 2021 });
+const customDifferentCase = custom.withTimeZone(new Custom("MOON/CHEESE")).with({ year: 2021 });
+
+assert.sameValue(custom.until(customSameCase, { largestUnit: "year" }).toString(), "P1Y");
+assert.throws(RangeError, () => custom.until(customDifferentCase, { largestUnit: "year" }));
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..2d9745b47a
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.until
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, hour: 15, timeZone: "UTC", calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.until({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/sub-minute-offset.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/sub-minute-offset.js
new file mode 100644
index 0000000000..b8fc742f2c
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/until/sub-minute-offset.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.until
+description: Fuzzy matching behaviour for UTC offset in ISO 8601 string with named time zones
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("Africa/Monrovia");
+const instance = new Temporal.ZonedDateTime(0n, timeZone);
+
+let result = instance.until("1970-01-01T00:44:30-00:44:30[Africa/Monrovia]");
+TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, "UTC offset rounded to minutes is accepted");
+
+result = instance.until("1970-01-01T00:44:30-00:44:30[Africa/Monrovia]");
+TemporalHelpers.assertDuration(result, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, "Unrounded sub-minute UTC offset also accepted");
+
+assert.throws(
+ RangeError,
+ () => instance.until("1970-01-01T00:44:30+00:44:30[+00:45"),
+ "minute rounding not supported for offset time zones"
+);
+
+const properties = {
+ offset: "-00:45",
+ year: 1970,
+ month: 1,
+ day: 1,
+ minute: 44,
+ second: 30,
+ timeZone
+};
+assert.throws(RangeError, () => instance.until(properties), "no fuzzy matching is done on offset in property bag");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/offset-property-sub-minute.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/offset-property-sub-minute.js
new file mode 100644
index 0000000000..5825173877
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/offset-property-sub-minute.js
@@ -0,0 +1,57 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.with
+description: Fuzzy matching behaviour with UTC offsets in ISO 8601 strings with named time zones and offset option
+includes: [temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const timeZone = new Temporal.TimeZone("Africa/Monrovia");
+const instance = Temporal.ZonedDateTime.from({ year: 1970, month: 1, day: 1, hour: 12, timeZone });
+assert.sameValue(instance.offset, "-00:44:30", "original offset");
+const properties = { day: 2, offset: "-00:45" };
+
+["ignore", "prefer"].forEach((offset) => {
+ const result = instance.with(properties, { offset });
+ assert.sameValue(result.epochNanoseconds, 132270_000_000_000n, `ignores new offset (offset=${offset})`);
+ assert.sameValue(result.offset, instance.offset, "offset property is unchanged");
+ TemporalHelpers.assertPlainDateTime(
+ result.toPlainDateTime(),
+ 1970,
+ 1,
+ "M01",
+ 2,
+ 12,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ "wall time is not shifted"
+ );
+});
+
+const result = instance.with(properties, { offset: "use" });
+assert.sameValue(result.epochNanoseconds, 132300_000_000_000n, "accepts HH:MM rounded offset (offset=use)");
+assert.sameValue(result.offset, instance.offset, "offset property is unchanged");
+TemporalHelpers.assertPlainDateTime(
+ result.toPlainDateTime(),
+ 1970,
+ 1,
+ "M01",
+ 2,
+ 12,
+ 0,
+ 30,
+ 0,
+ 0,
+ 0,
+ "wall time is shifted by the difference between exact and rounded offset"
+);
+
+assert.throws(RangeError, () => instance.with(properties, { offset: "reject" }), "no fuzzy matching is done in with()");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/with/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js
new file mode 100644
index 0000000000..4eaf9f7f82
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2022 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-temporal.zoneddatetime.prototype.withcalendar
+description: Calendar names are case-insensitive
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", {
+ dateAdd() {},
+ dateFromFields() {},
+ dateUntil() {},
+ day() {},
+ dayOfWeek() {},
+ dayOfYear() {},
+ daysInMonth() {},
+ daysInWeek() {},
+ daysInYear() {},
+ fields() {},
+ id: "replace-me",
+ inLeapYear() {},
+ mergeFields() {},
+ month() {},
+ monthCode() {},
+ monthDayFromFields() {},
+ monthsInYear() {},
+ weekOfYear() {},
+ year() {},
+ yearMonthFromFields() {},
+ yearOfWeek() {},
+});
+
+const arg = "jApAnEsE";;
+const result = instance.withCalendar(arg);
+assert.sameValue(result.calendarId, "japanese", "Calendar is case-insensitive");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withCalendar/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
new file mode 100644
index 0000000000..fce8492195
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/infinity-throws-rangeerror.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Temporal')) -- Temporal is not enabled unconditionally
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Throws if eraYear in the property bag is Infinity or -Infinity
+esid: sec-temporal.zoneddatetime.prototype.withplaindate
+includes: [compareArray.js, temporalHelpers.js]
+features: [Temporal]
+---*/
+
+const instance = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", "gregory");
+const base = { era: "ad", month: 5, day: 2, calendar: "gregory" };
+
+[Infinity, -Infinity].forEach((inf) => {
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: inf }), `eraYear property cannot be ${inf}`);
+
+ const calls = [];
+ const obj = TemporalHelpers.toPrimitiveObserver(calls, inf, "eraYear");
+ assert.throws(RangeError, () => instance.withPlainDate({ ...base, eraYear: obj }));
+ assert.compareArray(calls, ["get eraYear.valueOf", "call eraYear.valueOf"], "it fails after fetching the primitive value");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js
new file mode 100644
index 0000000000..60f74c2518
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/prototype/withPlainDate/shell.js
@@ -0,0 +1,2158 @@
+// GENERATED, DO NOT EDIT
+// file: temporalHelpers.js
+// Copyright (C) 2021 Igalia, S.L. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This defines helper objects and functions for testing Temporal.
+defines: [TemporalHelpers]
+features: [Symbol.species, Symbol.iterator, Temporal]
+---*/
+
+const ASCII_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/u;
+
+function formatPropertyName(propertyKey, objectName = "") {
+ switch (typeof propertyKey) {
+ case "symbol":
+ if (Symbol.keyFor(propertyKey) !== undefined) {
+ return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
+ } else if (propertyKey.description.startsWith('Symbol.')) {
+ return `${objectName}[${propertyKey.description}]`;
+ } else {
+ return `${objectName}[Symbol('${propertyKey.description}')]`
+ }
+ case "string":
+ if (propertyKey !== String(Number(propertyKey))) {
+ if (ASCII_IDENTIFIER.test(propertyKey)) {
+ return objectName ? `${objectName}.${propertyKey}` : propertyKey;
+ }
+ return `${objectName}['${propertyKey.replace(/'/g, "\\'")}']`
+ }
+ // fall through
+ default:
+ // integer or string integer-index
+ return `${objectName}[${propertyKey}]`;
+ }
+}
+
+const SKIP_SYMBOL = Symbol("Skip");
+
+var TemporalHelpers = {
+ /*
+ * Codes and maximum lengths of months in the ISO 8601 calendar.
+ */
+ ISOMonths: [
+ { month: 1, monthCode: "M01", daysInMonth: 31 },
+ { month: 2, monthCode: "M02", daysInMonth: 29 },
+ { month: 3, monthCode: "M03", daysInMonth: 31 },
+ { month: 4, monthCode: "M04", daysInMonth: 30 },
+ { month: 5, monthCode: "M05", daysInMonth: 31 },
+ { month: 6, monthCode: "M06", daysInMonth: 30 },
+ { month: 7, monthCode: "M07", daysInMonth: 31 },
+ { month: 8, monthCode: "M08", daysInMonth: 31 },
+ { month: 9, monthCode: "M09", daysInMonth: 30 },
+ { month: 10, monthCode: "M10", daysInMonth: 31 },
+ { month: 11, monthCode: "M11", daysInMonth: 30 },
+ { month: 12, monthCode: "M12", daysInMonth: 31 }
+ ],
+
+ /*
+ * assertDuration(duration, years, ..., nanoseconds[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * an expected value.
+ */
+ assertDuration(duration, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, hours, `${prefix}hours result:`);
+ assert.sameValue(duration.minutes, minutes, `${prefix}minutes result:`);
+ assert.sameValue(duration.seconds, seconds, `${prefix}seconds result:`);
+ assert.sameValue(duration.milliseconds, milliseconds, `${prefix}milliseconds result:`);
+ assert.sameValue(duration.microseconds, microseconds, `${prefix}microseconds result:`);
+ assert.sameValue(duration.nanoseconds, nanoseconds, `${prefix}nanoseconds result`);
+ },
+
+ /*
+ * assertDateDuration(duration, years, months, weeks, days, [, description]):
+ *
+ * Shorthand for asserting that each date field of a Temporal.Duration is
+ * equal to an expected value.
+ */
+ assertDateDuration(duration, years, months, weeks, days, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(duration instanceof Temporal.Duration, `${prefix}instanceof`);
+ assert.sameValue(duration.years, years, `${prefix}years result:`);
+ assert.sameValue(duration.months, months, `${prefix}months result:`);
+ assert.sameValue(duration.weeks, weeks, `${prefix}weeks result:`);
+ assert.sameValue(duration.days, days, `${prefix}days result:`);
+ assert.sameValue(duration.hours, 0, `${prefix}hours result should be zero:`);
+ assert.sameValue(duration.minutes, 0, `${prefix}minutes result should be zero:`);
+ assert.sameValue(duration.seconds, 0, `${prefix}seconds result should be zero:`);
+ assert.sameValue(duration.milliseconds, 0, `${prefix}milliseconds result should be zero:`);
+ assert.sameValue(duration.microseconds, 0, `${prefix}microseconds result should be zero:`);
+ assert.sameValue(duration.nanoseconds, 0, `${prefix}nanoseconds result should be zero:`);
+ },
+
+ /*
+ * assertDurationsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.Duration is equal to
+ * the corresponding field in another Temporal.Duration.
+ */
+ assertDurationsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Duration, `${prefix}expected value should be a Temporal.Duration`);
+ TemporalHelpers.assertDuration(actual, expected.years, expected.months, expected.weeks, expected.days, expected.hours, expected.minutes, expected.seconds, expected.milliseconds, expected.microseconds, expected.nanoseconds, description);
+ },
+
+ /*
+ * assertInstantsEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.Instants are of the correct type
+ * and equal according to their equals() methods.
+ */
+ assertInstantsEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.Instant, `${prefix}expected value should be a Temporal.Instant`);
+ assert(actual instanceof Temporal.Instant, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainDate(date, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDate is equal to
+ * an expected value. (Except the `calendar` property, since callers may want
+ * to assert either object equality with an object they put in there, or the
+ * value of date.calendarId.)
+ */
+ assertPlainDate(date, year, month, monthCode, day, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(date instanceof Temporal.PlainDate, `${prefix}instanceof`);
+ assert.sameValue(date.era, era, `${prefix}era result:`);
+ assert.sameValue(date.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(date.year, year, `${prefix}year result:`);
+ assert.sameValue(date.month, month, `${prefix}month result:`);
+ assert.sameValue(date.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(date.day, day, `${prefix}day result:`);
+ },
+
+ /*
+ * assertPlainDateTime(datetime, year, ..., nanosecond[, description[, era, eraYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainDateTime is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of datetime.calendarId.)
+ */
+ assertPlainDateTime(datetime, year, month, monthCode, day, hour, minute, second, millisecond, microsecond, nanosecond, description = "", era = undefined, eraYear = undefined) {
+ const prefix = description ? `${description}: ` : "";
+ assert(datetime instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert.sameValue(datetime.era, era, `${prefix}era result:`);
+ assert.sameValue(datetime.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(datetime.year, year, `${prefix}year result:`);
+ assert.sameValue(datetime.month, month, `${prefix}month result:`);
+ assert.sameValue(datetime.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(datetime.day, day, `${prefix}day result:`);
+ assert.sameValue(datetime.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(datetime.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(datetime.second, second, `${prefix}second result:`);
+ assert.sameValue(datetime.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(datetime.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(datetime.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their calendar internal slots are the same value.
+ */
+ assertPlainDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainDateTime, `${prefix}expected value should be a Temporal.PlainDateTime`);
+ assert(actual instanceof Temporal.PlainDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertPlainMonthDay(monthDay, monthCode, day[, description [, referenceISOYear]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainMonthDay is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of monthDay.calendarId().)
+ */
+ assertPlainMonthDay(monthDay, monthCode, day, description = "", referenceISOYear = 1972) {
+ const prefix = description ? `${description}: ` : "";
+ assert(monthDay instanceof Temporal.PlainMonthDay, `${prefix}instanceof`);
+ assert.sameValue(monthDay.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(monthDay.day, day, `${prefix}day result:`);
+ assert.sameValue(monthDay.getISOFields().isoYear, referenceISOYear, `${prefix}referenceISOYear result:`);
+ },
+
+ /*
+ * assertPlainTime(time, hour, ..., nanosecond[, description]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainTime is equal to
+ * an expected value.
+ */
+ assertPlainTime(time, hour, minute, second, millisecond, microsecond, nanosecond, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(time instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert.sameValue(time.hour, hour, `${prefix}hour result:`);
+ assert.sameValue(time.minute, minute, `${prefix}minute result:`);
+ assert.sameValue(time.second, second, `${prefix}second result:`);
+ assert.sameValue(time.millisecond, millisecond, `${prefix}millisecond result:`);
+ assert.sameValue(time.microsecond, microsecond, `${prefix}microsecond result:`);
+ assert.sameValue(time.nanosecond, nanosecond, `${prefix}nanosecond result:`);
+ },
+
+ /*
+ * assertPlainTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.PlainTimes are of the correct
+ * type and equal according to their equals() methods.
+ */
+ assertPlainTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.PlainTime, `${prefix}expected value should be a Temporal.PlainTime`);
+ assert(actual instanceof Temporal.PlainTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ },
+
+ /*
+ * assertPlainYearMonth(yearMonth, year, month, monthCode[, description[, era, eraYear, referenceISODay]]):
+ *
+ * Shorthand for asserting that each field of a Temporal.PlainYearMonth is
+ * equal to an expected value. (Except the `calendar` property, since callers
+ * may want to assert either object equality with an object they put in there,
+ * or the value of yearMonth.calendarId.)
+ */
+ assertPlainYearMonth(yearMonth, year, month, monthCode, description = "", era = undefined, eraYear = undefined, referenceISODay = 1) {
+ const prefix = description ? `${description}: ` : "";
+ assert(yearMonth instanceof Temporal.PlainYearMonth, `${prefix}instanceof`);
+ assert.sameValue(yearMonth.era, era, `${prefix}era result:`);
+ assert.sameValue(yearMonth.eraYear, eraYear, `${prefix}eraYear result:`);
+ assert.sameValue(yearMonth.year, year, `${prefix}year result:`);
+ assert.sameValue(yearMonth.month, month, `${prefix}month result:`);
+ assert.sameValue(yearMonth.monthCode, monthCode, `${prefix}monthCode result:`);
+ assert.sameValue(yearMonth.getISOFields().isoDay, referenceISODay, `${prefix}referenceISODay result:`);
+ },
+
+ /*
+ * assertZonedDateTimesEqual(actual, expected[, description]):
+ *
+ * Shorthand for asserting that two Temporal.ZonedDateTimes are of the correct
+ * type, equal according to their equals() methods, and additionally that
+ * their time zones and calendar internal slots are the same value.
+ */
+ assertZonedDateTimesEqual(actual, expected, description = "") {
+ const prefix = description ? `${description}: ` : "";
+ assert(expected instanceof Temporal.ZonedDateTime, `${prefix}expected value should be a Temporal.ZonedDateTime`);
+ assert(actual instanceof Temporal.ZonedDateTime, `${prefix}instanceof`);
+ assert(actual.equals(expected), `${prefix}equals method`);
+ assert.sameValue(actual.timeZone, expected.timeZone, `${prefix}time zone same value:`);
+ assert.sameValue(
+ actual.getISOFields().calendar,
+ expected.getISOFields().calendar,
+ `${prefix}calendar same value:`
+ );
+ },
+
+ /*
+ * assertUnreachable(description):
+ *
+ * Helper for asserting that code is not executed. This is useful for
+ * assertions that methods of user calendars and time zones are not called.
+ */
+ assertUnreachable(description) {
+ let message = "This code should not be executed";
+ if (description) {
+ message = `${message}: ${description}`;
+ }
+ throw new Test262Error(message);
+ },
+
+ /*
+ * checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls):
+ *
+ * When an options object with a largestUnit property is synthesized inside
+ * Temporal and passed to user code such as calendar.dateUntil(), the value of
+ * the largestUnit property should be in the singular form, even if the input
+ * was given in the plural form.
+ * (This doesn't apply when the options object is passed through verbatim.)
+ *
+ * func(calendar, largestUnit, index) is the operation under test. It's called
+ * with an instance of a calendar that keeps track of which largestUnit is
+ * passed to dateUntil(), each key of expectedLargestUnitCalls in turn, and
+ * the key's numerical index in case the function needs to generate test data
+ * based on the index. At the end, the actual values passed to dateUntil() are
+ * compared with the array values of expectedLargestUnitCalls.
+ */
+ checkCalendarDateUntilLargestUnitSingular(func, expectedLargestUnitCalls) {
+ const actual = [];
+
+ class DateUntilOptionsCalendar extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateUntil(earlier, later, options) {
+ actual.push(options.largestUnit);
+ return super.dateUntil(earlier, later, options);
+ }
+
+ toString() {
+ return "date-until-options";
+ }
+ }
+
+ const calendar = new DateUntilOptionsCalendar();
+ Object.entries(expectedLargestUnitCalls).forEach(([largestUnit, expected], index) => {
+ func(calendar, largestUnit, index);
+ assert.compareArray(actual, expected, `largestUnit passed to calendar.dateUntil() for largestUnit ${largestUnit}`);
+ actual.splice(0); // empty it for the next check
+ });
+ },
+
+ /*
+ * checkPlainDateTimeConversionFastPath(func):
+ *
+ * ToTemporalDate and ToTemporalTime should both, if given a
+ * Temporal.PlainDateTime instance, convert to the desired type by reading the
+ * PlainDateTime's internal slots, rather than calling any getters.
+ *
+ * func(datetime, calendar) is the actual operation to test, that must
+ * internally call the abstract operation ToTemporalDate or ToTemporalTime.
+ * It is passed a Temporal.PlainDateTime instance, as well as the instance's
+ * calendar object (so that it doesn't have to call the calendar getter itself
+ * if it wants to make any assertions about the calendar.)
+ */
+ checkPlainDateTimeConversionFastPath(func, message = "checkPlainDateTimeConversionFastPath") {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const datetime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDateTime.prototype);
+ ["year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(datetime, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return {
+ toString() {
+ actual.push(`toString ${formatPropertyName(property)}`);
+ return value.toString();
+ },
+ valueOf() {
+ actual.push(`valueOf ${formatPropertyName(property)}`);
+ return value;
+ },
+ };
+ },
+ });
+ });
+ Object.defineProperty(datetime, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(datetime, calendar);
+ assert.compareArray(actual, expected, `${message}: property getters not called`);
+ },
+
+ /*
+ * Check that an options bag that accepts units written in the singular form,
+ * also accepts the same units written in the plural form.
+ * func(unit) should call the method with the appropriate options bag
+ * containing unit as a value. This will be called twice for each element of
+ * validSingularUnits, once with singular and once with plural, and the
+ * results of each pair should be the same (whether a Temporal object or a
+ * primitive value.)
+ */
+ checkPluralUnitsAccepted(func, validSingularUnits) {
+ const plurals = {
+ year: 'years',
+ month: 'months',
+ week: 'weeks',
+ day: 'days',
+ hour: 'hours',
+ minute: 'minutes',
+ second: 'seconds',
+ millisecond: 'milliseconds',
+ microsecond: 'microseconds',
+ nanosecond: 'nanoseconds',
+ };
+
+ validSingularUnits.forEach((unit) => {
+ const singularValue = func(unit);
+ const pluralValue = func(plurals[unit]);
+ const desc = `Plural ${plurals[unit]} produces the same result as singular ${unit}`;
+ if (singularValue instanceof Temporal.Duration) {
+ TemporalHelpers.assertDurationsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.Instant) {
+ TemporalHelpers.assertInstantsEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainDateTime) {
+ TemporalHelpers.assertPlainDateTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.PlainTime) {
+ TemporalHelpers.assertPlainTimesEqual(pluralValue, singularValue, desc);
+ } else if (singularValue instanceof Temporal.ZonedDateTime) {
+ TemporalHelpers.assertZonedDateTimesEqual(pluralValue, singularValue, desc);
+ } else {
+ assert.sameValue(pluralValue, singularValue);
+ }
+ });
+ },
+
+ /*
+ * checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc):
+ *
+ * Checks the type handling of the roundingIncrement option.
+ * checkFunc(roundingIncrement) is a function which takes the value of
+ * roundingIncrement to test, and calls the method under test with it,
+ * returning the result. assertTrueResultFunc(result, description) should
+ * assert that result is the expected result with roundingIncrement: true, and
+ * assertObjectResultFunc(result, description) should assert that result is
+ * the expected result with roundingIncrement being an object with a valueOf()
+ * method.
+ */
+ checkRoundingIncrementOptionWrongType(checkFunc, assertTrueResultFunc, assertObjectResultFunc) {
+ // null converts to 0, which is out of range
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to either 0 or 1, and 1 is allowed
+ const trueResult = checkFunc(true);
+ assertTrueResultFunc(trueResult, "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols and BigInts cannot convert to numbers
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ assert.throws(TypeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their valueOf() methods when converting to a number
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ "get roundingIncrement.valueOf",
+ "call roundingIncrement.valueOf",
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, 2, "roundingIncrement");
+ const objectResult = checkFunc(observer);
+ assertObjectResultFunc(objectResult, "object with valueOf");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc):
+ *
+ * Checks the type handling of a string option, of which there are several in
+ * Temporal.
+ * propertyName is the name of the option, and value is the value that
+ * assertFunc should expect it to have.
+ * checkFunc(value) is a function which takes the value of the option to test,
+ * and calls the method under test with it, returning the result.
+ * assertFunc(result, description) should assert that result is the expected
+ * result with the option value being an object with a toString() method
+ * which returns the given value.
+ */
+ checkStringOptionWrongType(propertyName, value, checkFunc, assertFunc) {
+ // null converts to the string "null", which is an invalid string value
+ assert.throws(RangeError, () => checkFunc(null), "null");
+ // Booleans convert to the strings "true" or "false", which are invalid
+ assert.throws(RangeError, () => checkFunc(true), "true");
+ assert.throws(RangeError, () => checkFunc(false), "false");
+ // Symbols cannot convert to strings
+ assert.throws(TypeError, () => checkFunc(Symbol()), "symbol");
+ // Numbers convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2), "number");
+ // BigInts convert to strings which are invalid
+ assert.throws(RangeError, () => checkFunc(2n), "bigint");
+
+ // Objects prefer their toString() methods when converting to a string
+ assert.throws(RangeError, () => checkFunc({}), "plain object");
+
+ const expected = [
+ `get ${propertyName}.toString`,
+ `call ${propertyName}.toString`,
+ ];
+ const actual = [];
+ const observer = TemporalHelpers.toPrimitiveObserver(actual, value, propertyName);
+ const result = checkFunc(observer);
+ assertFunc(result, "object with toString");
+ assert.compareArray(actual, expected, "order of operations");
+ },
+
+ /*
+ * checkSubclassingIgnored(construct, constructArgs, method, methodArgs,
+ * resultAssertions):
+ *
+ * Methods of Temporal classes that return a new instance of the same class,
+ * must not take the constructor of a subclass into account, nor the @@species
+ * property. This helper runs tests to ensure this.
+ *
+ * construct(...constructArgs) must yield a valid instance of the Temporal
+ * class. instance[method](...methodArgs) is the method call under test, which
+ * must also yield a valid instance of the same Temporal class, not a
+ * subclass. See below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnored(...args) {
+ this.checkSubclassConstructorNotObject(...args);
+ this.checkSubclassConstructorUndefined(...args);
+ this.checkSubclassConstructorThrows(...args);
+ this.checkSubclassConstructorNotCalled(...args);
+ this.checkSubclassSpeciesInvalidResult(...args);
+ this.checkSubclassSpeciesNotAConstructor(...args);
+ this.checkSubclassSpeciesNull(...args);
+ this.checkSubclassSpeciesUndefined(...args);
+ this.checkSubclassSpeciesThrows(...args);
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the instance with
+ * various primitive values does not affect the returned new instance.
+ */
+ checkSubclassConstructorNotObject(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = value;
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ },
+
+ /*
+ * Checks that replacing the 'constructor' property of the subclass with
+ * undefined does not affect the returned new instance.
+ */
+ checkSubclassConstructorUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = undefined;
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that making the 'constructor' property of the instance throw when
+ * called does not affect the returned new instance.
+ */
+ checkSubclassConstructorThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+ const instance = new construct(...constructArgs);
+ Object.defineProperty(instance, "constructor", {
+ get() {
+ throw new CustomError();
+ }
+ });
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Checks that when subclassing, the subclass constructor is not called by
+ * the method under test.
+ */
+ checkSubclassConstructorNotCalled(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's a
+ * constructor that returns a non-object value.
+ */
+ checkSubclassSpeciesInvalidResult(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: function() {
+ return value;
+ },
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's not a
+ * constructor.
+ */
+ checkSubclassSpeciesNotAConstructor(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ [Symbol.species]: value,
+ };
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype, description);
+ resultAssertions(result);
+ }
+
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "Symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "plain object");
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's null.
+ */
+ checkSubclassSpeciesNull(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: null,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it's
+ * undefined.
+ */
+ checkSubclassSpeciesUndefined(construct, constructArgs, method, methodArgs, resultAssertions) {
+ let called = 0;
+
+ class MySubclass extends construct {
+ constructor() {
+ ++called;
+ super(...constructArgs);
+ }
+ }
+
+ const instance = new MySubclass();
+ assert.sameValue(called, 1);
+
+ MySubclass.prototype.constructor = {
+ [Symbol.species]: undefined,
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(called, 1);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that the constructor's @@species property is ignored when it throws,
+ * i.e. it is not called at all.
+ */
+ checkSubclassSpeciesThrows(construct, constructArgs, method, methodArgs, resultAssertions) {
+ function CustomError() {}
+
+ const instance = new construct(...constructArgs);
+ instance.constructor = {
+ get [Symbol.species]() {
+ throw new CustomError();
+ },
+ };
+
+ const result = instance[method](...methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ },
+
+ /*
+ * checkSubclassingIgnoredStatic(construct, method, methodArgs, resultAssertions):
+ *
+ * Static methods of Temporal classes that return a new instance of the class,
+ * must not use the this-value as a constructor. This helper runs tests to
+ * ensure this.
+ *
+ * construct[method](...methodArgs) is the static method call under test, and
+ * must yield a valid instance of the Temporal class, not a subclass. See
+ * below for the individual tests that this runs.
+ * resultAssertions() is a function that performs additional assertions on the
+ * instance returned by the method under test.
+ */
+ checkSubclassingIgnoredStatic(...args) {
+ this.checkStaticInvalidReceiver(...args);
+ this.checkStaticReceiverNotCalled(...args);
+ this.checkThisValueNotCalled(...args);
+ },
+
+ /*
+ * Check that calling the static method with a receiver that's not callable,
+ * still calls the intrinsic constructor.
+ */
+ checkStaticInvalidReceiver(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const result = construct[method].apply(value, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that calling the static method with a receiver that returns a value
+ * that's not callable, still calls the intrinsic constructor.
+ */
+ checkStaticReceiverNotCalled(construct, method, methodArgs, resultAssertions) {
+ function check(value, description) {
+ const receiver = function () {
+ return value;
+ };
+ const result = construct[method].apply(receiver, methodArgs);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ }
+
+ check(undefined, "undefined");
+ check(null, "null");
+ check(true, "true");
+ check("test", "string");
+ check(Symbol(), "symbol");
+ check(7, "number");
+ check(7n, "bigint");
+ check({}, "Non-callable object");
+ },
+
+ /*
+ * Check that the receiver isn't called.
+ */
+ checkThisValueNotCalled(construct, method, methodArgs, resultAssertions) {
+ let called = false;
+
+ class MySubclass extends construct {
+ constructor(...args) {
+ called = true;
+ super(...args);
+ }
+ }
+
+ const result = MySubclass[method](...methodArgs);
+ assert.sameValue(called, false);
+ assert.sameValue(Object.getPrototypeOf(result), construct.prototype);
+ resultAssertions(result);
+ },
+
+ /*
+ * Check that any iterable returned from a custom time zone's
+ * getPossibleInstantsFor() method is exhausted.
+ * The custom time zone object is passed in to func().
+ * expected is an array of strings representing the expected calls to the
+ * getPossibleInstantsFor() method. The PlainDateTimes that it is called with,
+ * are compared (using their toString() results) with the array.
+ */
+ checkTimeZonePossibleInstantsIterable(func, expected) {
+ // A custom time zone that returns an iterable instead of an array from its
+ // getPossibleInstantsFor() method, and for testing purposes skips
+ // 00:00-01:00 UTC on January 1, 2030, and repeats 00:00-01:00 UTC+1 on
+ // January 3, 2030. Otherwise identical to the UTC time zone.
+ class TimeZonePossibleInstantsIterable extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ this.getPossibleInstantsForCallCount = 0;
+ this.getPossibleInstantsForCalledWith = [];
+ this.getPossibleInstantsForReturns = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "Custom/Iterable";
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (Temporal.Instant.compare(instant, "2030-01-01T00:00Z") >= 0 &&
+ Temporal.Instant.compare(instant, "2030-01-03T01:00Z") < 0) {
+ return 3600_000_000_000;
+ } else {
+ return 0;
+ }
+ }
+
+ getPossibleInstantsFor(dateTime) {
+ this.getPossibleInstantsForCallCount++;
+ this.getPossibleInstantsForCalledWith.push(dateTime);
+
+ // Fake DST transition
+ let retval = super.getPossibleInstantsFor(dateTime);
+ if (dateTime.toPlainDate().equals("2030-01-01") && dateTime.hour === 0) {
+ retval = [];
+ } else if (dateTime.toPlainDate().equals("2030-01-03") && dateTime.hour === 0) {
+ retval.push(retval[0].subtract({ hours: 1 }));
+ } else if (dateTime.year === 2030 && dateTime.month === 1 && dateTime.day >= 1 && dateTime.day <= 2) {
+ retval[0] = retval[0].subtract({ hours: 1 });
+ }
+
+ this.getPossibleInstantsForReturns.push(retval);
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.getPossibleInstantsForCallCount - 1,
+ timeZone: this,
+ *[Symbol.iterator]() {
+ yield* this.timeZone.getPossibleInstantsForReturns[this.callIndex];
+ this.timeZone.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+
+ const timeZone = new TimeZonePossibleInstantsIterable();
+ func(timeZone);
+
+ assert.sameValue(timeZone.getPossibleInstantsForCallCount, expected.length, "getPossibleInstantsFor() method called correct number of times");
+
+ for (let index = 0; index < expected.length; index++) {
+ assert.sameValue(timeZone.getPossibleInstantsForCalledWith[index].toString(), expected[index], "getPossibleInstantsFor() called with expected PlainDateTime");
+ assert(timeZone.iteratorExhausted[index], "iterated through the whole iterable");
+ }
+ },
+
+ /*
+ * Check that any calendar-carrying Temporal object has its [[Calendar]]
+ * internal slot read by ToTemporalCalendar, and does not fetch the calendar
+ * by calling getters.
+ * The custom calendar object is passed in to func() so that it can do its
+ * own additional assertions involving the calendar if necessary. (Sometimes
+ * there is nothing to assert as the calendar isn't stored anywhere that can
+ * be asserted about.)
+ */
+ checkToTemporalCalendarFastPath(func) {
+ class CalendarFastPathCheck extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ monthDayFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.monthDayFromFields(...args).getISOFields();
+ return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ }
+
+ yearMonthFromFields(...args) {
+ const { isoYear, isoMonth, isoDay } = super.yearMonthFromFields(...args).getISOFields();
+ return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ }
+
+ toString() {
+ return "fast-path-check";
+ }
+ }
+ const calendar = new CalendarFastPathCheck();
+
+ const plainDate = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const plainDateTime = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321, calendar);
+ const plainMonthDay = new Temporal.PlainMonthDay(5, 2, calendar);
+ const plainYearMonth = new Temporal.PlainYearMonth(2000, 5, calendar);
+ const zonedDateTime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
+
+ [plainDate, plainDateTime, plainMonthDay, plainYearMonth, zonedDateTime].forEach((temporalObject) => {
+ const actual = [];
+ const expected = [];
+
+ Object.defineProperty(temporalObject, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(temporalObject, calendar);
+ assert.compareArray(actual, expected, "calendar getter not called");
+ });
+ },
+
+ checkToTemporalInstantFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const datetime = new Temporal.ZonedDateTime(1_000_000_000_987_654_321n, "UTC");
+ Object.defineProperty(datetime, 'toString', {
+ get() {
+ actual.push("get toString");
+ return function (options) {
+ actual.push("call toString");
+ return Temporal.ZonedDateTime.prototype.toString.call(this, options);
+ };
+ },
+ });
+
+ func(datetime);
+ assert.compareArray(actual, expected, "toString not called");
+ },
+
+ checkToTemporalPlainDateTimeFastPath(func) {
+ const actual = [];
+ const expected = [];
+
+ const calendar = new Temporal.Calendar("iso8601");
+ const date = new Temporal.PlainDate(2000, 5, 2, calendar);
+ const prototypeDescrs = Object.getOwnPropertyDescriptors(Temporal.PlainDate.prototype);
+ ["year", "month", "monthCode", "day"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ const value = prototypeDescrs[property].get.call(this);
+ return TemporalHelpers.toPrimitiveObserver(actual, value, property);
+ },
+ });
+ });
+ ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"].forEach((property) => {
+ Object.defineProperty(date, property, {
+ get() {
+ actual.push(`get ${formatPropertyName(property)}`);
+ return undefined;
+ },
+ });
+ });
+ Object.defineProperty(date, "calendar", {
+ get() {
+ actual.push("get calendar");
+ return calendar;
+ },
+ });
+
+ func(date, calendar);
+ assert.compareArray(actual, expected, "property getters not called");
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * fromFields methods are always called with a null-prototype fields object.
+ */
+ calendarCheckFieldsPrototypePollution() {
+ class CalendarCheckFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ }
+
+ // toString must remain "iso8601", so that some methods don't throw due to
+ // incompatible calendars
+
+ dateFromFields(fields, options = {}) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "dateFromFields should be called with null-prototype fields object");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options = {}) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "yearMonthFromFields should be called with null-prototype fields object");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options = {}) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "monthDayFromFields should be called with null-prototype fields object");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+
+ return new CalendarCheckFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that the
+ * mergeFields() method is always called with null-prototype fields objects.
+ */
+ calendarCheckMergeFieldsPrototypePollution() {
+ class CalendarCheckMergeFieldsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-null-proto";
+ }
+
+ mergeFields(fields, additionalFields) {
+ this.mergeFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(fields), null, "mergeFields should be called with null-prototype fields object (first argument)");
+ assert.sameValue(Object.getPrototypeOf(additionalFields), null, "mergeFields should be called with null-prototype fields object (second argument)");
+ return super.mergeFields(fields, additionalFields);
+ }
+ }
+
+ return new CalendarCheckMergeFieldsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar used in prototype pollution checks. Verifies that methods
+ * are always called with a null-prototype options object.
+ */
+ calendarCheckOptionsPrototypePollution() {
+ class CalendarCheckOptionsPrototypePollution extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.yearMonthFromFieldsCallCount = 0;
+ this.dateUntilCallCount = 0;
+ }
+
+ toString() {
+ return "options-null-proto";
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "yearMonthFromFields should be called with null-prototype options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ dateUntil(one, two, options) {
+ this.dateUntilCallCount++;
+ assert.sameValue(Object.getPrototypeOf(options), null, "dateUntil should be called with null-prototype options");
+ return super.dateUntil(one, two, options);
+ }
+ }
+
+ return new CalendarCheckOptionsPrototypePollution();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with the
+ * options parameter having the value undefined.
+ */
+ calendarDateAddUndefinedOptions() {
+ class CalendarDateAddUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ }
+
+ toString() {
+ return "dateadd-undef-options";
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert.sameValue(options, undefined, "dateAdd shouldn't be called with options");
+ return super.dateAdd(date, duration, options);
+ }
+ }
+ return new CalendarDateAddUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that asserts its dateAdd() method is called with a
+ * PlainDate instance. Optionally, it also asserts that the PlainDate instance
+ * is the specific object `this.specificPlainDate`, if it is set by the
+ * calling code.
+ */
+ calendarDateAddPlainDateInstance() {
+ class CalendarDateAddPlainDateInstance extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateAddCallCount = 0;
+ this.specificPlainDate = undefined;
+ }
+
+ toString() {
+ return "dateadd-plain-date-instance";
+ }
+
+ dateFromFields(...args) {
+ return super.dateFromFields(...args).withCalendar(this);
+ }
+
+ dateAdd(date, duration, options) {
+ this.dateAddCallCount++;
+ assert(date instanceof Temporal.PlainDate, "dateAdd() should be called with a PlainDate instance");
+ if (this.dateAddCallCount === 1 && this.specificPlainDate) {
+ assert.sameValue(date, this.specificPlainDate, `dateAdd() should be called first with the specific PlainDate instance ${this.specificPlainDate}`);
+ }
+ return super.dateAdd(date, duration, options).withCalendar(this);
+ }
+ }
+ return new CalendarDateAddPlainDateInstance();
+ },
+
+ /*
+ * A custom calendar that returns an iterable instead of an array from its
+ * fields() method, otherwise identical to the ISO calendar.
+ */
+ calendarFieldsIterable() {
+ class CalendarFieldsIterable extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.fieldsCallCount = 0;
+ this.fieldsCalledWith = [];
+ this.iteratorExhausted = [];
+ }
+
+ toString() {
+ return "fields-iterable";
+ }
+
+ fields(fieldNames) {
+ this.fieldsCallCount++;
+ this.fieldsCalledWith.push(fieldNames.slice());
+ this.iteratorExhausted.push(false);
+ return {
+ callIndex: this.fieldsCallCount - 1,
+ calendar: this,
+ *[Symbol.iterator]() {
+ yield* this.calendar.fieldsCalledWith[this.callIndex];
+ this.calendar.iteratorExhausted[this.callIndex] = true;
+ },
+ };
+ }
+ }
+ return new CalendarFieldsIterable();
+ },
+
+ /*
+ * A custom calendar that asserts its ...FromFields() methods are called with
+ * the options parameter having the value undefined.
+ */
+ calendarFromFieldsUndefinedOptions() {
+ class CalendarFromFieldsUndefinedOptions extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "from-fields-undef-options";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "dateFromFields shouldn't be called with options");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "yearMonthFromFields shouldn't be called with options");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ assert.sameValue(options, undefined, "monthDayFromFields shouldn't be called with options");
+ return super.monthDayFromFields(fields, options);
+ }
+ }
+ return new CalendarFromFieldsUndefinedOptions();
+ },
+
+ /*
+ * A custom calendar that modifies the fields object passed in to
+ * dateFromFields, sabotaging its time properties.
+ */
+ calendarMakeInfinityTime() {
+ class CalendarMakeInfinityTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ fields.hour = Infinity;
+ fields.minute = Infinity;
+ fields.second = Infinity;
+ fields.millisecond = Infinity;
+ fields.microsecond = Infinity;
+ fields.nanosecond = Infinity;
+ return retval;
+ }
+ }
+ return new CalendarMakeInfinityTime();
+ },
+
+ /*
+ * A custom calendar that defines getters on the fields object passed into
+ * dateFromFields that throw, sabotaging its time properties.
+ */
+ calendarMakeInvalidGettersTime() {
+ class CalendarMakeInvalidGettersTime extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+
+ dateFromFields(fields, options) {
+ const retval = super.dateFromFields(fields, options);
+ const throwingDescriptor = {
+ get() {
+ throw new Test262Error("reading a sabotaged time field");
+ },
+ };
+ Object.defineProperties(fields, {
+ hour: throwingDescriptor,
+ minute: throwingDescriptor,
+ second: throwingDescriptor,
+ millisecond: throwingDescriptor,
+ microsecond: throwingDescriptor,
+ nanosecond: throwingDescriptor,
+ });
+ return retval;
+ }
+ }
+ return new CalendarMakeInvalidGettersTime();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a proxy object with
+ * all of its Get and HasProperty operations observable, as well as adding a
+ * "shouldNotBeCopied": true property.
+ */
+ calendarMergeFieldsGetters() {
+ class CalendarMergeFieldsGetters extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ this.mergeFieldsReturnOperations = [];
+ }
+
+ toString() {
+ return "merge-fields-getters";
+ }
+
+ dateFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ assert.sameValue(fields.shouldNotBeCopied, undefined, "extra fields should not be copied");
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields(fields, additionalFields) {
+ const retval = super.mergeFields(fields, additionalFields);
+ retval._calendar = this;
+ retval.shouldNotBeCopied = true;
+ return new Proxy(retval, {
+ get(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`get ${key}`);
+ const result = target[key];
+ if (result === undefined) {
+ return undefined;
+ }
+ return TemporalHelpers.toPrimitiveObserver(target._calendar.mergeFieldsReturnOperations, result, key);
+ },
+ has(target, key) {
+ target._calendar.mergeFieldsReturnOperations.push(`has ${key}`);
+ return key in target;
+ },
+ });
+ }
+ }
+ return new CalendarMergeFieldsGetters();
+ },
+
+ /*
+ * A custom calendar whose mergeFields() method returns a primitive value,
+ * given by @primitive, and which records the number of calls made to its
+ * dateFromFields(), yearMonthFromFields(), and monthDayFromFields() methods.
+ */
+ calendarMergeFieldsReturnsPrimitive(primitive) {
+ class CalendarMergeFieldsPrimitive extends Temporal.Calendar {
+ constructor(mergeFieldsReturnValue) {
+ super("iso8601");
+ this._mergeFieldsReturnValue = mergeFieldsReturnValue;
+ this.dateFromFieldsCallCount = 0;
+ this.monthDayFromFieldsCallCount = 0;
+ this.yearMonthFromFieldsCallCount = 0;
+ }
+
+ toString() {
+ return "merge-fields-primitive";
+ }
+
+ dateFromFields(fields, options) {
+ this.dateFromFieldsCallCount++;
+ return super.dateFromFields(fields, options);
+ }
+
+ yearMonthFromFields(fields, options) {
+ this.yearMonthFromFieldsCallCount++;
+ return super.yearMonthFromFields(fields, options);
+ }
+
+ monthDayFromFields(fields, options) {
+ this.monthDayFromFieldsCallCount++;
+ return super.monthDayFromFields(fields, options);
+ }
+
+ mergeFields() {
+ return this._mergeFieldsReturnValue;
+ }
+ }
+ return new CalendarMergeFieldsPrimitive(primitive);
+ },
+
+ /*
+ * A custom calendar whose fields() method returns the same value as the
+ * iso8601 calendar, with the addition of extraFields provided as parameter.
+ */
+ calendarWithExtraFields(fields) {
+ class CalendarWithExtraFields extends Temporal.Calendar {
+ constructor(extraFields) {
+ super("iso8601");
+ this._extraFields = extraFields;
+ }
+
+ fields(fieldNames) {
+ return super.fields(fieldNames).concat(this._extraFields);
+ }
+ }
+
+ return new CalendarWithExtraFields(fields);
+ },
+
+ /*
+ * crossDateLineTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single transition where the time zone moves from one side of the
+ * International Date Line to the other, for the purpose of testing time zone
+ * calculations without depending on system time zone data.
+ *
+ * The transition occurs at epoch second 1325239200 and goes from offset
+ * -10:00 to +14:00. In other words, the time zone skips the whole calendar
+ * day of 2011-12-30. This is the same as the real-life transition in the
+ * Pacific/Apia time zone.
+ */
+ crossDateLineTimeZone() {
+ const { compare } = Temporal.PlainDate;
+ const skippedDay = new Temporal.PlainDate(2011, 12, 30);
+ const transitionEpoch = 1325239200_000_000_000n;
+ const beforeOffset = new Temporal.TimeZone("-10:00");
+ const afterOffset = new Temporal.TimeZone("+14:00");
+
+ class CrossDateLineTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("+14:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) {
+ return beforeOffset.getOffsetNanosecondsFor(instant);
+ }
+ return afterOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ const comparison = compare(datetime.toPlainDate(), skippedDay);
+ if (comparison === 0) {
+ return [];
+ }
+ if (comparison < 0) {
+ return [beforeOffset.getInstantFor(datetime)];
+ }
+ return [afterOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < transitionEpoch) return new Temporal.Instant(transitionEpoch);
+ return null;
+ }
+
+ toString() {
+ return "Custom/Date_Line";
+ }
+ }
+ return new CrossDateLineTimeZone();
+ },
+
+ /*
+ * observeProperty(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls to its accessors to the array @calls.
+ */
+ observeProperty(calls, object, propertyName, value, objectName = "") {
+ Object.defineProperty(object, propertyName, {
+ get() {
+ calls.push(`get ${formatPropertyName(propertyName, objectName)}`);
+ return value;
+ },
+ set(v) {
+ calls.push(`set ${formatPropertyName(propertyName, objectName)}`);
+ }
+ });
+ },
+
+ /*
+ * observeMethod(calls, object, propertyName, value):
+ *
+ * Defines an own property @object.@propertyName with value @value, that
+ * will log any calls of @value to the array @calls.
+ */
+ observeMethod(calls, object, propertyName, objectName = "") {
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ calls.push(`call ${formatPropertyName(propertyName, objectName)}`);
+ return method.apply(object, arguments);
+ };
+ },
+
+ /*
+ * Used for substituteMethod to indicate default behavior instead of a
+ * substituted value
+ */
+ SUBSTITUTE_SKIP: SKIP_SYMBOL,
+
+ /*
+ * substituteMethod(object, propertyName, values):
+ *
+ * Defines an own property @object.@propertyName that will, for each
+ * subsequent call to the method previously defined as
+ * @object.@propertyName:
+ * - Call the method, if no more values remain
+ * - Call the method, if the value in @values for the corresponding call
+ * is SUBSTITUTE_SKIP
+ * - Otherwise, return the corresponding value in @value
+ */
+ substituteMethod(object, propertyName, values) {
+ let calls = 0;
+ const method = object[propertyName];
+ object[propertyName] = function () {
+ if (calls >= values.length) {
+ return method.apply(object, arguments);
+ } else if (values[calls] === SKIP_SYMBOL) {
+ calls++;
+ return method.apply(object, arguments);
+ } else {
+ return values[calls++];
+ }
+ };
+ },
+
+ /*
+ * calendarObserver:
+ * A custom calendar that behaves exactly like the ISO 8601 calendar but
+ * tracks calls to any of its methods, and Get/Has operations on its
+ * properties, by appending messages to an array. This is for the purpose of
+ * testing order of operations that are observable from user code.
+ * objectName is used in the log.
+ */
+ calendarObserver(calls, objectName, methodOverrides = {}) {
+ function removeExtraHasPropertyChecks(objectName, calls) {
+ // Inserting the tracking calendar into the return values of methods
+ // that we chain up into the ISO calendar for, causes extra HasProperty
+ // checks, which we observe. This removes them so that we don't leak
+ // implementation details of the helper into the test code.
+ assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.year`);
+ assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
+ assert.sameValue(calls.pop(), `has ${objectName}.month`);
+ assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.id`);
+ assert.sameValue(calls.pop(), `has ${objectName}.fields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
+ assert.sameValue(calls.pop(), `has ${objectName}.day`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
+ assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
+ }
+
+ const iso8601 = new Temporal.Calendar("iso8601");
+ const trackingMethods = {
+ dateFromFields(...args) {
+ calls.push(`call ${objectName}.dateFromFields`);
+ if ('dateFromFields' in methodOverrides) {
+ const value = methodOverrides.dateFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ yearMonthFromFields(...args) {
+ calls.push(`call ${objectName}.yearMonthFromFields`);
+ if ('yearMonthFromFields' in methodOverrides) {
+ const value = methodOverrides.yearMonthFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.yearMonthFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ monthDayFromFields(...args) {
+ calls.push(`call ${objectName}.monthDayFromFields`);
+ if ('monthDayFromFields' in methodOverrides) {
+ const value = methodOverrides.monthDayFromFields;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.monthDayFromFields(...args);
+ // Replace the calendar in the result with the call-tracking calendar
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ dateAdd(...args) {
+ calls.push(`call ${objectName}.dateAdd`);
+ if ('dateAdd' in methodOverrides) {
+ const value = methodOverrides.dateAdd;
+ return typeof value === "function" ? value(...args) : value;
+ }
+ const originalResult = iso8601.dateAdd(...args);
+ const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
+ const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
+ removeExtraHasPropertyChecks(objectName, calls);
+ return result;
+ },
+ id: "iso8601",
+ };
+ // Automatically generate the other methods that don't need any custom code
+ [
+ "dateUntil",
+ "day",
+ "dayOfWeek",
+ "dayOfYear",
+ "daysInMonth",
+ "daysInWeek",
+ "daysInYear",
+ "era",
+ "eraYear",
+ "fields",
+ "inLeapYear",
+ "mergeFields",
+ "month",
+ "monthCode",
+ "monthsInYear",
+ "toString",
+ "weekOfYear",
+ "year",
+ "yearOfWeek",
+ ].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return iso8601[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom calendar that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ calendarThrowEverything() {
+ class CalendarThrowEverything extends Temporal.Calendar {
+ constructor() {
+ super("iso8601");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ dateFromFields() {
+ TemporalHelpers.assertUnreachable("dateFromFields should not be called");
+ }
+ yearMonthFromFields() {
+ TemporalHelpers.assertUnreachable("yearMonthFromFields should not be called");
+ }
+ monthDayFromFields() {
+ TemporalHelpers.assertUnreachable("monthDayFromFields should not be called");
+ }
+ dateAdd() {
+ TemporalHelpers.assertUnreachable("dateAdd should not be called");
+ }
+ dateUntil() {
+ TemporalHelpers.assertUnreachable("dateUntil should not be called");
+ }
+ era() {
+ TemporalHelpers.assertUnreachable("era should not be called");
+ }
+ eraYear() {
+ TemporalHelpers.assertUnreachable("eraYear should not be called");
+ }
+ year() {
+ TemporalHelpers.assertUnreachable("year should not be called");
+ }
+ month() {
+ TemporalHelpers.assertUnreachable("month should not be called");
+ }
+ monthCode() {
+ TemporalHelpers.assertUnreachable("monthCode should not be called");
+ }
+ day() {
+ TemporalHelpers.assertUnreachable("day should not be called");
+ }
+ fields() {
+ TemporalHelpers.assertUnreachable("fields should not be called");
+ }
+ mergeFields() {
+ TemporalHelpers.assertUnreachable("mergeFields should not be called");
+ }
+ }
+
+ return new CalendarThrowEverything();
+ },
+
+ /*
+ * oneShiftTimeZone(shiftInstant, shiftNanoseconds):
+ *
+ * In the case of a spring-forward time zone offset transition (skipped time),
+ * and disambiguation === 'earlier', BuiltinTimeZoneGetInstantFor subtracts a
+ * negative number of nanoseconds from a PlainDateTime, which should balance
+ * with the microseconds field.
+ *
+ * This returns an instance of a custom time zone class which skips a length
+ * of time equal to shiftNanoseconds (a number), at the Temporal.Instant
+ * shiftInstant. Before shiftInstant, it's identical to UTC, and after
+ * shiftInstant it's a constant-offset time zone.
+ *
+ * It provides a getPossibleInstantsForCalledWith member which is an array
+ * with the result of calling toString() on any PlainDateTimes passed to
+ * getPossibleInstantsFor().
+ */
+ oneShiftTimeZone(shiftInstant, shiftNanoseconds) {
+ class OneShiftTimeZone extends Temporal.TimeZone {
+ constructor(shiftInstant, shiftNanoseconds) {
+ super("+00:00");
+ this._shiftInstant = shiftInstant;
+ this._epoch1 = shiftInstant.epochNanoseconds;
+ this._epoch2 = this._epoch1 + BigInt(shiftNanoseconds);
+ this._shiftNanoseconds = shiftNanoseconds;
+ this._shift = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, this._shiftNanoseconds);
+ this.getPossibleInstantsForCalledWith = [];
+ }
+
+ _isBeforeShift(instant) {
+ return instant.epochNanoseconds < this._epoch1;
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ return this._isBeforeShift(instant) ? 0 : this._shiftNanoseconds;
+ }
+
+ getPossibleInstantsFor(plainDateTime) {
+ this.getPossibleInstantsForCalledWith.push(plainDateTime.toString({ calendarName: "never" }));
+ const [instant] = super.getPossibleInstantsFor(plainDateTime);
+ if (this._shiftNanoseconds > 0) {
+ if (this._isBeforeShift(instant)) return [instant];
+ if (instant.epochNanoseconds < this._epoch2) return [];
+ return [instant.subtract(this._shift)];
+ }
+ if (instant.epochNanoseconds < this._epoch2) return [instant];
+ const shifted = instant.subtract(this._shift);
+ if (this._isBeforeShift(instant)) return [instant, shifted];
+ return [shifted];
+ }
+
+ getNextTransition(instant) {
+ return this._isBeforeShift(instant) ? this._shiftInstant : null;
+ }
+
+ getPreviousTransition(instant) {
+ return this._isBeforeShift(instant) ? null : this._shiftInstant;
+ }
+
+ toString() {
+ return "Custom/One_Shift";
+ }
+ }
+ return new OneShiftTimeZone(shiftInstant, shiftNanoseconds);
+ },
+
+ /*
+ * propertyBagObserver():
+ * Returns an object that behaves like the given propertyBag but tracks Get
+ * and Has operations on any of its properties, by appending messages to an
+ * array. If the value of a property in propertyBag is a primitive, the value
+ * of the returned object's property will additionally be a
+ * TemporalHelpers.toPrimitiveObserver that will track calls to its toString
+ * and valueOf methods in the same array. This is for the purpose of testing
+ * order of operations that are observable from user code. objectName is used
+ * in the log.
+ */
+ propertyBagObserver(calls, propertyBag, objectName) {
+ return new Proxy(propertyBag, {
+ ownKeys(target) {
+ calls.push(`ownKeys ${objectName}`);
+ return Reflect.ownKeys(target);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ calls.push(`getOwnPropertyDescriptor ${formatPropertyName(key, objectName)}`);
+ return Reflect.getOwnPropertyDescriptor(target, key);
+ },
+ get(target, key, receiver) {
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ const result = Reflect.get(target, key, receiver);
+ if (result === undefined) {
+ return undefined;
+ }
+ if ((result !== null && typeof result === "object") || typeof result === "function") {
+ return result;
+ }
+ return TemporalHelpers.toPrimitiveObserver(calls, result, `${formatPropertyName(key, objectName)}`);
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * specificOffsetTimeZone():
+ *
+ * This returns an instance of a custom time zone class, which returns a
+ * specific custom value from its getOffsetNanosecondsFrom() method. This is
+ * for the purpose of testing the validation of what this method returns.
+ *
+ * It also returns an empty array from getPossibleInstantsFor(), so as to
+ * trigger calls to getOffsetNanosecondsFor() when used from the
+ * BuiltinTimeZoneGetInstantFor operation.
+ */
+ specificOffsetTimeZone(offsetValue) {
+ class SpecificOffsetTimeZone extends Temporal.TimeZone {
+ constructor(offsetValue) {
+ super("UTC");
+ this._offsetValue = offsetValue;
+ }
+
+ getOffsetNanosecondsFor() {
+ return this._offsetValue;
+ }
+
+ getPossibleInstantsFor(dt) {
+ if (typeof this._offsetValue !== 'number' || Math.abs(this._offsetValue) >= 86400e9 || isNaN(this._offsetValue)) return [];
+ const zdt = dt.toZonedDateTime("UTC").add({ nanoseconds: -this._offsetValue });
+ return [zdt.toInstant()];
+ }
+
+ get id() {
+ return this.getOffsetStringFor(new Temporal.Instant(0n));
+ }
+ }
+ return new SpecificOffsetTimeZone(offsetValue);
+ },
+
+ /*
+ * springForwardFallBackTimeZone():
+ *
+ * This returns an instance of a custom time zone class that implements one
+ * single spring-forward/fall-back transition, for the purpose of testing the
+ * disambiguation option, without depending on system time zone data.
+ *
+ * The spring-forward occurs at epoch second 954669600 (2000-04-02T02:00
+ * local) and goes from offset -08:00 to -07:00.
+ *
+ * The fall-back occurs at epoch second 972810000 (2000-10-29T02:00 local) and
+ * goes from offset -07:00 to -08:00.
+ */
+ springForwardFallBackTimeZone() {
+ const { compare } = Temporal.PlainDateTime;
+ const springForwardLocal = new Temporal.PlainDateTime(2000, 4, 2, 2);
+ const springForwardEpoch = 954669600_000_000_000n;
+ const fallBackLocal = new Temporal.PlainDateTime(2000, 10, 29, 1);
+ const fallBackEpoch = 972810000_000_000_000n;
+ const winterOffset = new Temporal.TimeZone('-08:00');
+ const summerOffset = new Temporal.TimeZone('-07:00');
+
+ class SpringForwardFallBackTimeZone extends Temporal.TimeZone {
+ constructor() {
+ super("-08:00");
+ }
+
+ getOffsetNanosecondsFor(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch ||
+ instant.epochNanoseconds >= fallBackEpoch) {
+ return winterOffset.getOffsetNanosecondsFor(instant);
+ }
+ return summerOffset.getOffsetNanosecondsFor(instant);
+ }
+
+ getPossibleInstantsFor(datetime) {
+ if (compare(datetime, springForwardLocal) >= 0 && compare(datetime, springForwardLocal.add({ hours: 1 })) < 0) {
+ return [];
+ }
+ if (compare(datetime, fallBackLocal) >= 0 && compare(datetime, fallBackLocal.add({ hours: 1 })) < 0) {
+ return [summerOffset.getInstantFor(datetime), winterOffset.getInstantFor(datetime)];
+ }
+ if (compare(datetime, springForwardLocal) < 0 || compare(datetime, fallBackLocal) >= 0) {
+ return [winterOffset.getInstantFor(datetime)];
+ }
+ return [summerOffset.getInstantFor(datetime)];
+ }
+
+ getPreviousTransition(instant) {
+ if (instant.epochNanoseconds > fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ if (instant.epochNanoseconds > springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ return null;
+ }
+
+ getNextTransition(instant) {
+ if (instant.epochNanoseconds < springForwardEpoch) return new Temporal.Instant(springForwardEpoch);
+ if (instant.epochNanoseconds < fallBackEpoch) return new Temporal.Instant(fallBackEpoch);
+ return null;
+ }
+
+ get id() {
+ return "Custom/Spring_Fall";
+ }
+
+ toString() {
+ return "Custom/Spring_Fall";
+ }
+ }
+ return new SpringForwardFallBackTimeZone();
+ },
+
+ /*
+ * timeZoneObserver:
+ * A custom calendar that behaves exactly like the UTC time zone but tracks
+ * calls to any of its methods, and Get/Has operations on its properties, by
+ * appending messages to an array. This is for the purpose of testing order of
+ * operations that are observable from user code. objectName is used in the
+ * log. methodOverrides is an optional object containing properties with the
+ * same name as Temporal.TimeZone methods. If the property value is a function
+ * it will be called with the proper arguments instead of the UTC method.
+ * Otherwise, the property value will be returned directly.
+ */
+ timeZoneObserver(calls, objectName, methodOverrides = {}) {
+ const utc = new Temporal.TimeZone("UTC");
+ const trackingMethods = {
+ id: "UTC",
+ };
+ // Automatically generate the methods
+ ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "toString"].forEach((methodName) => {
+ trackingMethods[methodName] = function (...args) {
+ calls.push(`call ${formatPropertyName(methodName, objectName)}`);
+ if (methodName in methodOverrides) {
+ const value = methodOverrides[methodName];
+ return typeof value === "function" ? value(...args) : value;
+ }
+ return utc[methodName](...args);
+ };
+ });
+ return new Proxy(trackingMethods, {
+ get(target, key, receiver) {
+ const result = Reflect.get(target, key, receiver);
+ calls.push(`get ${formatPropertyName(key, objectName)}`);
+ return result;
+ },
+ has(target, key) {
+ calls.push(`has ${formatPropertyName(key, objectName)}`);
+ return Reflect.has(target, key);
+ },
+ });
+ },
+
+ /*
+ * A custom time zone that does not allow any of its methods to be called, for
+ * the purpose of asserting that a particular operation does not call into
+ * user code.
+ */
+ timeZoneThrowEverything() {
+ class TimeZoneThrowEverything extends Temporal.TimeZone {
+ constructor() {
+ super("UTC");
+ }
+ getOffsetNanosecondsFor() {
+ TemporalHelpers.assertUnreachable("getOffsetNanosecondsFor should not be called");
+ }
+ getPossibleInstantsFor() {
+ TemporalHelpers.assertUnreachable("getPossibleInstantsFor should not be called");
+ }
+ toString() {
+ TemporalHelpers.assertUnreachable("toString should not be called");
+ }
+ }
+
+ return new TimeZoneThrowEverything();
+ },
+
+ /*
+ * Returns an object that will append logs of any Gets or Calls of its valueOf
+ * or toString properties to the array calls. Both valueOf and toString will
+ * return the actual primitiveValue. propertyName is used in the log.
+ */
+ toPrimitiveObserver(calls, primitiveValue, propertyName) {
+ return {
+ get valueOf() {
+ calls.push(`get ${propertyName}.valueOf`);
+ return function () {
+ calls.push(`call ${propertyName}.valueOf`);
+ return primitiveValue;
+ };
+ },
+ get toString() {
+ calls.push(`get ${propertyName}.toString`);
+ return function () {
+ calls.push(`call ${propertyName}.toString`);
+ if (primitiveValue === undefined) return undefined;
+ return primitiveValue.toString();
+ };
+ },
+ };
+ },
+
+ /*
+ * An object containing further methods that return arrays of ISO strings, for
+ * testing parsers.
+ */
+ ISO: {
+ /*
+ * PlainMonthDay strings that are not valid.
+ */
+ plainMonthDayStringsInvalid() {
+ return [
+ "11-18junk",
+ "11-18[u-ca=gregory]",
+ "11-18[u-ca=hebrew]",
+ ];
+ },
+
+ /*
+ * PlainMonthDay strings that are valid and that should produce October 1st.
+ */
+ plainMonthDayStringsValid() {
+ return [
+ "10-01",
+ "1001",
+ "1965-10-01",
+ "1976-10-01T152330.1+00:00",
+ "19761001T15:23:30.1+00:00",
+ "1976-10-01T15:23:30.1+0000",
+ "1976-10-01T152330.1+0000",
+ "19761001T15:23:30.1+0000",
+ "19761001T152330.1+00:00",
+ "19761001T152330.1+0000",
+ "+001976-10-01T152330.1+00:00",
+ "+0019761001T15:23:30.1+00:00",
+ "+001976-10-01T15:23:30.1+0000",
+ "+001976-10-01T152330.1+0000",
+ "+0019761001T15:23:30.1+0000",
+ "+0019761001T152330.1+00:00",
+ "+0019761001T152330.1+0000",
+ "1976-10-01T15:23:00",
+ "1976-10-01T15:23",
+ "1976-10-01T15",
+ "1976-10-01",
+ "--10-01",
+ "--1001",
+ ];
+ },
+
+ /*
+ * PlainTime strings that may be mistaken for PlainMonthDay or
+ * PlainYearMonth strings, and so require a time designator.
+ */
+ plainTimeStringsAmbiguous() {
+ const ambiguousStrings = [
+ "2021-12", // ambiguity between YYYY-MM and HHMM-UU
+ "2021-12[-12:00]", // ditto, TZ does not disambiguate
+ "1214", // ambiguity between MMDD and HHMM
+ "0229", // ditto, including MMDD that doesn't occur every year
+ "1130", // ditto, including DD that doesn't occur in every month
+ "12-14", // ambiguity between MM-DD and HH-UU
+ "12-14[-14:00]", // ditto, TZ does not disambiguate
+ "202112", // ambiguity between YYYYMM and HHMMSS
+ "202112[UTC]", // ditto, TZ does not disambiguate
+ ];
+ // Adding a calendar annotation to one of these strings must not cause
+ // disambiguation in favour of time.
+ const stringsWithCalendar = ambiguousStrings.map((s) => s + '[u-ca=iso8601]');
+ return ambiguousStrings.concat(stringsWithCalendar);
+ },
+
+ /*
+ * PlainTime strings that are of similar form to PlainMonthDay and
+ * PlainYearMonth strings, but are not ambiguous due to components that
+ * aren't valid as months or days.
+ */
+ plainTimeStringsUnambiguous() {
+ return [
+ "2021-13", // 13 is not a month
+ "202113", // ditto
+ "2021-13[-13:00]", // ditto
+ "202113[-13:00]", // ditto
+ "0000-00", // 0 is not a month
+ "000000", // ditto
+ "0000-00[UTC]", // ditto
+ "000000[UTC]", // ditto
+ "1314", // 13 is not a month
+ "13-14", // ditto
+ "1232", // 32 is not a day
+ "0230", // 30 is not a day in February
+ "0631", // 31 is not a day in June
+ "0000", // 0 is neither a month nor a day
+ "00-00", // ditto
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are not valid.
+ */
+ plainYearMonthStringsInvalid() {
+ return [
+ "2020-13",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November
+ * 1976 in the ISO 8601 calendar.
+ */
+ plainYearMonthStringsValid() {
+ return [
+ "1976-11",
+ "1976-11-10",
+ "1976-11-01T09:00:00+00:00",
+ "1976-11-01T00:00:00+05:00",
+ "197611",
+ "+00197611",
+ "1976-11-18T15:23:30.1\u221202:00",
+ "1976-11-18T152330.1+00:00",
+ "19761118T15:23:30.1+00:00",
+ "1976-11-18T15:23:30.1+0000",
+ "1976-11-18T152330.1+0000",
+ "19761118T15:23:30.1+0000",
+ "19761118T152330.1+00:00",
+ "19761118T152330.1+0000",
+ "+001976-11-18T152330.1+00:00",
+ "+0019761118T15:23:30.1+00:00",
+ "+001976-11-18T15:23:30.1+0000",
+ "+001976-11-18T152330.1+0000",
+ "+0019761118T15:23:30.1+0000",
+ "+0019761118T152330.1+00:00",
+ "+0019761118T152330.1+0000",
+ "1976-11-18T15:23",
+ "1976-11-18T15",
+ "1976-11-18",
+ ];
+ },
+
+ /*
+ * PlainYearMonth-like strings that are valid and should produce November of
+ * the ISO year -9999.
+ */
+ plainYearMonthStringsValidNegativeYear() {
+ return [
+ "\u2212009999-11",
+ ];
+ },
+ }
+};
diff --git a/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/ZonedDateTime/shell.js
diff --git a/js/src/tests/test262/intl402/Temporal/browser.js b/js/src/tests/test262/intl402/Temporal/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/browser.js
diff --git a/js/src/tests/test262/intl402/Temporal/shell.js b/js/src/tests/test262/intl402/Temporal/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/Temporal/shell.js
diff --git a/js/src/tests/test262/intl402/TypedArray/browser.js b/js/src/tests/test262/intl402/TypedArray/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/browser.js
diff --git a/js/src/tests/test262/intl402/TypedArray/prototype/browser.js b/js/src/tests/test262/intl402/TypedArray/prototype/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/prototype/browser.js
diff --git a/js/src/tests/test262/intl402/TypedArray/prototype/shell.js b/js/src/tests/test262/intl402/TypedArray/prototype/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/prototype/shell.js
diff --git a/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/browser.js b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/browser.js
diff --git a/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/calls-toLocaleString-number-elements.js b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/calls-toLocaleString-number-elements.js
new file mode 100644
index 0000000000..c6b895e5db
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/calls-toLocaleString-number-elements.js
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sup-array.prototype.tolocalestring
+description: >
+ Ensure "toLocaleString" is called with locale and options on number elements.
+includes: [testTypedArray.js]
+features: [TypedArray]
+---*/
+
+var n = 0;
+
+var locale = "th-u-nu-thai";
+var options = {
+ minimumFractionDigits: 3
+};
+
+var expected = n.toLocaleString(locale, options);
+
+testWithTypedArrayConstructors(function(TA) {
+ assert.sameValue(new TA([n]).toLocaleString(locale, options), expected);
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/shell.js b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/shell.js
new file mode 100644
index 0000000000..05fecd6ef1
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/prototype/toLocaleString/shell.js
@@ -0,0 +1,161 @@
+// GENERATED, DO NOT EDIT
+// file: testTypedArray.js
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Collection of functions used to assert the correctness of TypedArray objects.
+defines:
+ - floatArrayConstructors
+ - nonClampedIntArrayConstructors
+ - intArrayConstructors
+ - typedArrayConstructors
+ - TypedArray
+ - testWithTypedArrayConstructors
+ - nonAtomicsFriendlyTypedArrayConstructors
+ - testWithAtomicsFriendlyTypedArrayConstructors
+ - testWithNonAtomicsFriendlyTypedArrayConstructors
+ - testTypedArrayConversions
+---*/
+
+var floatArrayConstructors = [
+ Float64Array,
+ Float32Array
+];
+
+var nonClampedIntArrayConstructors = [
+ Int32Array,
+ Int16Array,
+ Int8Array,
+ Uint32Array,
+ Uint16Array,
+ Uint8Array
+];
+
+var intArrayConstructors = nonClampedIntArrayConstructors.concat([Uint8ClampedArray]);
+
+// Float16Array is a newer feature
+// adding it to this list unconditionally would cause implementations lacking it to fail every test which uses it
+if (typeof Float16Array !== 'undefined') {
+ floatArrayConstructors.push(Float16Array);
+}
+
+/**
+ * Array containing every non-bigint typed array constructor.
+ */
+
+var typedArrayConstructors = floatArrayConstructors.concat(intArrayConstructors);
+
+/**
+ * The %TypedArray% intrinsic constructor function.
+ */
+var TypedArray = Object.getPrototypeOf(Int8Array);
+
+/**
+ * Callback for testing a typed array constructor.
+ *
+ * @callback typedArrayConstructorCallback
+ * @param {Function} Constructor the constructor object to test with.
+ */
+
+/**
+ * Calls the provided function for every typed array constructor.
+ *
+ * @param {typedArrayConstructorCallback} f - the function to call for each typed array constructor.
+ * @param {Array} selected - An optional Array with filtered typed arrays
+ */
+function testWithTypedArrayConstructors(f, selected) {
+ var constructors = selected || typedArrayConstructors;
+ for (var i = 0; i < constructors.length; ++i) {
+ var constructor = constructors[i];
+ try {
+ f(constructor);
+ } catch (e) {
+ e.message += " (Testing with " + constructor.name + ".)";
+ throw e;
+ }
+ }
+}
+
+var nonAtomicsFriendlyTypedArrayConstructors = floatArrayConstructors.concat([Uint8ClampedArray]);
+/**
+ * Calls the provided function for every non-"Atomics Friendly" typed array constructor.
+ *
+ * @param {typedArrayConstructorCallback} f - the function to call for each typed array constructor.
+ * @param {Array} selected - An optional Array with filtered typed arrays
+ */
+function testWithNonAtomicsFriendlyTypedArrayConstructors(f) {
+ testWithTypedArrayConstructors(f, nonAtomicsFriendlyTypedArrayConstructors);
+}
+
+/**
+ * Calls the provided function for every "Atomics Friendly" typed array constructor.
+ *
+ * @param {typedArrayConstructorCallback} f - the function to call for each typed array constructor.
+ * @param {Array} selected - An optional Array with filtered typed arrays
+ */
+function testWithAtomicsFriendlyTypedArrayConstructors(f) {
+ testWithTypedArrayConstructors(f, [
+ Int32Array,
+ Int16Array,
+ Int8Array,
+ Uint32Array,
+ Uint16Array,
+ Uint8Array,
+ ]);
+}
+
+/**
+ * Helper for conversion operations on TypedArrays, the expected values
+ * properties are indexed in order to match the respective value for each
+ * TypedArray constructor
+ * @param {Function} fn - the function to call for each constructor and value.
+ * will be called with the constructor, value, expected
+ * value, and a initial value that can be used to avoid
+ * a false positive with an equivalent expected value.
+ */
+function testTypedArrayConversions(byteConversionValues, fn) {
+ var values = byteConversionValues.values;
+ var expected = byteConversionValues.expected;
+
+ testWithTypedArrayConstructors(function(TA) {
+ var name = TA.name.slice(0, -5);
+
+ return values.forEach(function(value, index) {
+ var exp = expected[name][index];
+ var initial = 0;
+ if (exp === 0) {
+ initial = 1;
+ }
+ fn(TA, value, exp, initial);
+ });
+ });
+}
+
+/**
+ * Checks if the given argument is one of the float-based TypedArray constructors.
+ *
+ * @param {constructor} ctor - the value to check
+ * @returns {boolean}
+ */
+function isFloatTypedArrayConstructor(arg) {
+ return floatArrayConstructors.indexOf(arg) !== -1;
+}
+
+/**
+ * Determines the precision of the given float-based TypedArray constructor.
+ *
+ * @param {constructor} ctor - the value to check
+ * @returns {string} "half", "single", or "double" for Float16Array, Float32Array, and Float64Array respectively.
+ */
+function floatTypedArrayConstructorPrecision(FA) {
+ if (typeof Float16Array !== "undefined" && FA === Float16Array) {
+ return "half";
+ } else if (FA === Float32Array) {
+ return "single";
+ } else if (FA === Float64Array) {
+ return "double";
+ } else {
+ throw new Error("Malformed test - floatTypedArrayConstructorPrecision called with non-float TypedArray");
+ }
+}
diff --git a/js/src/tests/test262/intl402/TypedArray/shell.js b/js/src/tests/test262/intl402/TypedArray/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/TypedArray/shell.js
diff --git a/js/src/tests/test262/intl402/browser.js b/js/src/tests/test262/intl402/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/test262/intl402/browser.js
diff --git a/js/src/tests/test262/intl402/constructors-string-and-single-element-array.js b/js/src/tests/test262/intl402/constructors-string-and-single-element-array.js
new file mode 100644
index 0000000000..a73c7581c9
--- /dev/null
+++ b/js/src/tests/test262/intl402/constructors-string-and-single-element-array.js
@@ -0,0 +1,73 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_3
+description: >
+ Tests that a single string instead of a locale list is treated as
+ the locale list containing that string.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var validAndInvalidLanguageTags = [
+ "de", // ISO 639 language code
+ "de-DE", // + ISO 3166-1 country code
+ "DE-de", // tags are case-insensitive
+ "cmn", // ISO 639 language code
+ "cmn-Hans", // + script code
+ "CMN-hANS", // tags are case-insensitive
+ "cmn-hans-cn", // + ISO 3166-1 country code
+ "es-419", // + UN M.49 region code
+ "es-419-u-nu-latn-cu-bob", // + Unicode locale extension sequence
+ "i-klingon", // grandfathered tag
+ "cmn-hans-cn-t-ca-u-ca-x-t-u", // singleton subtags can also be used as private use subtags
+ "de-gregory-u-ca-gregory", // variant and extension subtags may be the same
+ "de_DE",
+ "DE_de",
+ "cmn_Hans",
+ "cmn-hans_cn",
+ "es_419",
+ "es-419-u-nu-latn-cu_bob",
+ "i_klingon",
+ "cmn-hans-cn-t-ca-u-ca-x_t-u",
+ "enochian_enochian",
+ "de-gregory_u-ca-gregory",
+ "i", // singleton alone
+ "x", // private use without subtag
+ "u", // extension singleton in first place
+ "419", // region code in first place
+ "u-nu-latn-cu-bob", // extension sequence without language
+ "hans-cmn-cn", // "hans" could theoretically be a 4-letter language code,
+ // but those can't be followed by extlang codes.
+ "cmn-hans-cn-u-u", // duplicate singleton
+ "cmn-hans-cn-t-u-ca-u", // duplicate singleton
+ "de-gregory-gregory" // duplicate variant
+];
+
+testWithIntlConstructors(function (Constructor) {
+ validAndInvalidLanguageTags.forEach(function (locale) {
+ var obj1, obj2, locale1, locale2, error1, error2;
+ try {
+ obj1 = new Constructor(locale);
+ locale1 = obj1.resolvedOptions().locale;
+ } catch (e) {
+ error1 = e;
+ }
+ try {
+ obj2 = new Constructor([locale]);
+ locale2 = obj2.resolvedOptions().locale;
+ } catch (e) {
+ error2 = e;
+ }
+
+ assert.sameValue((error1 === undefined), (error2 === undefined), "Single locale string " + locale + " was " + (error1 === undefined ? "accepted" : "rejected") + ", but locale list containing that string wasn't.");
+ if (error1 === undefined) {
+ assert.sameValue(locale1, locale2, "Single locale string " + locale + " results in " + locale1 + ", but locale list [" + locale + "] results in " + locale2 + ".");
+ } else {
+ assert.sameValue(error1.name, error2.name, "Single locale string " + locale + " results in error " + error1.name + ", but locale list [" + locale + "] results in error " + error2.name + ".");
+ }
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/constructors-taint-Object-prototype-2.js b/js/src/tests/test262/intl402/constructors-taint-Object-prototype-2.js
new file mode 100644
index 0000000000..b2c829293c
--- /dev/null
+++ b/js/src/tests/test262/intl402/constructors-taint-Object-prototype-2.js
@@ -0,0 +1,20 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.5_6
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["dataLocale", "nu", "ca", "co", "locale"]);
+
+testWithIntlConstructors(function (Constructor) {
+ var locale = new Constructor(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+ assert(isCanonicalizedStructurallyValidLanguageTag(locale), "Constructor returns invalid locale " + locale + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/constructors-taint-Object-prototype.js b/js/src/tests/test262/intl402/constructors-taint-Object-prototype.js
new file mode 100644
index 0000000000..5363296a83
--- /dev/null
+++ b/js/src/tests/test262/intl402/constructors-taint-Object-prototype.js
@@ -0,0 +1,20 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.3_5
+description: >
+ Tests that the behavior of a Record is not affected by
+ adversarial changes to Object.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintProperties(["locale", "extension", "extensionIndex"]);
+
+testWithIntlConstructors(function (Constructor) {
+ var locale = new Constructor(undefined, {localeMatcher: "lookup"}).resolvedOptions().locale;
+ assert(isCanonicalizedStructurallyValidLanguageTag(locale), "Constructor returns invalid locale " + locale + ".");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/default-locale-is-canonicalized.js b/js/src/tests/test262/intl402/default-locale-is-canonicalized.js
new file mode 100644
index 0000000000..36a31b2ce2
--- /dev/null
+++ b/js/src/tests/test262/intl402/default-locale-is-canonicalized.js
@@ -0,0 +1,18 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.2.4
+description: >
+ Tests that the default locale is a String value representing the
+ structurally valid and canonicalized BCP 47 language tag.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ assert(isCanonicalizedStructurallyValidLanguageTag(defaultLocale), "Default locale \"" + defaultLocale + "\" is not canonicalized and structurally valid language tag.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/default-locale-is-supported.js b/js/src/tests/test262/intl402/default-locale-is-supported.js
new file mode 100644
index 0000000000..27d55154b3
--- /dev/null
+++ b/js/src/tests/test262/intl402/default-locale-is-supported.js
@@ -0,0 +1,17 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.1_a
+description: Tests that default locale is available.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var supportedLocales = Constructor.supportedLocalesOf([defaultLocale]);
+ assert.notSameValue(supportedLocales.indexOf(defaultLocale), -1, "Default locale is not reported as available.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/fallback-locales-are-supported.js b/js/src/tests/test262/intl402/fallback-locales-are-supported.js
new file mode 100644
index 0000000000..e26c3835fa
--- /dev/null
+++ b/js/src/tests/test262/intl402/fallback-locales-are-supported.js
@@ -0,0 +1,37 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.1_b
+description: >
+ Tests that appropriate fallback locales are provided for
+ supported locales.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+features: [Array.prototype.includes]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ // The test is only valid under "lookup" localeMatcher
+ var info = getLocaleSupportInfo(Constructor, {localeMatcher: "lookup"});
+ for (var locale of info.supported) {
+ var match = /^([a-z]{2,3})(-[A-Z][a-z]{3})?(-(?:[A-Z]{2}|[0-9]{3}))?$/.exec(locale);
+ assert.notSameValue(match, null, "Locale " + locale + " is supported, but can't be parsed.")
+
+ var [language, script, region] = match.slice(1);
+
+ if (script !== undefined) {
+ var fallback = language + script;
+ assert(info.supported.includes(fallback) || info.byFallback.includes(fallback),
+ "Locale " + locale + " is supported, but fallback " + fallback + " isn't.");
+ }
+
+ if (region !== undefined) {
+ var fallback = language + region;
+ assert(info.supported.includes(fallback) || info.byFallback.includes(fallback),
+ "Locale " + locale + " is supported, but fallback " + fallback + " isn't.");
+ }
+ }
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/language-tags-canonicalized.js b/js/src/tests/test262/intl402/language-tags-canonicalized.js
new file mode 100644
index 0000000000..f3c4035142
--- /dev/null
+++ b/js/src/tests/test262/intl402/language-tags-canonicalized.js
@@ -0,0 +1,55 @@
+// Copyright 2011-2012 Norbert Lindenberg. All rights reserved.
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.2.3
+description: Tests that language tags are canonicalized in return values.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var canonicalizedTags = {
+ "de": ["de"],
+ "de-DE": ["de-DE", "de"],
+ "DE-de": ["de-DE", "de"],
+ "cmn": ["zh"],
+ "CMN-hANS": ["zh-Hans", "zh"],
+ "cmn-hans-cn": ["zh-Hans-CN", "zh-Hans", "zh"],
+ "es-419": ["es-419", "es"],
+ "es-419-u-nu-latn": ["es-419-u-nu-latn", "es-419", "es", "es-u-nu-latn"],
+ // -u-ca is incomplete, so it will not show up in resolvedOptions().locale
+ "cmn-hans-cn-u-ca-t-ca-x-t-u": ["zh-Hans-CN-t-ca-u-ca-x-t-u", "zh-Hans-CN-t-ca-x-t-u", "zh-Hans-CN-t-ca-x-t", "zh-Hans-CN-t-ca", "zh-Hans-CN", "zh-Hans", "zh"],
+ "de-gregory-u-ca-gregory": ["de-gregory-u-ca-gregory", "de-gregory", "de-u-ca-gregory", "de"],
+ "sgn-GR": ["gss"],
+ "ji": ["yi"],
+ "de-DD": ["de-DE", "de"],
+ "in": ["id"],
+};
+
+// make sure the data above is correct
+Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ canonicalizedTags[tag].forEach(function (canonicalTag) {
+ assert(isCanonicalizedStructurallyValidLanguageTag(canonicalTag), "Test data \"" + canonicalTag + "\" is not canonicalized and structurally valid language tag.");
+ });
+});
+
+// now the actual test
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ Object.getOwnPropertyNames(canonicalizedTags).forEach(function (tag) {
+ // use lookup locale matcher to keep the set of possible return values predictable
+
+ // Variant 1: construct an object and see whether its locale is canonicalized.
+ // In this variant, shortened forms or the default locale may be returned
+ var object = new Constructor([tag], { localeMatcher: "lookup" });
+ var locale = object.resolvedOptions().locale;
+ assert(canonicalizedTags[tag].indexOf(locale) !== -1 || locale === defaultLocale, "For " + tag + " got " + locale + "; expected one of " + canonicalizedTags[tag].join(", ") + ".");
+
+ // Variant 2: get the supported locales. If the tag is supported, it should be returned canonicalized but unshortened
+ var supported = Constructor.supportedLocalesOf([tag]);
+ assert(supported.length === 0 || supported[0] === canonicalizedTags[tag][0], "For " + tag + " got " + supported[0] + "; expected " + canonicalizedTags[tag][0] + ".");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/language-tags-invalid.js b/js/src/tests/test262/intl402/language-tags-invalid.js
new file mode 100644
index 0000000000..0579ed24c3
--- /dev/null
+++ b/js/src/tests/test262/intl402/language-tags-invalid.js
@@ -0,0 +1,24 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.2.2_c
+description: >
+ Tests that language tags with invalid subtag sequences are not
+ accepted.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var invalidLanguageTags = getInvalidLanguageTags();
+
+testWithIntlConstructors(function (Constructor) {
+ invalidLanguageTags.forEach(function (tag) {
+ // this must throw an exception for an invalid language tag
+ assert.throws(RangeError, function() {
+ var obj = new Constructor([tag]);
+ }, "Invalid language tag " + tag + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/language-tags-valid.js b/js/src/tests/test262/intl402/language-tags-valid.js
new file mode 100644
index 0000000000..f9a7372d52
--- /dev/null
+++ b/js/src/tests/test262/intl402/language-tags-valid.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.2.2_a
+description: Tests that structurally valid language tags are accepted.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var validLanguageTags = [
+ "de", // ISO 639 language code
+ "de-DE", // + ISO 3166-1 country code
+ "DE-de", // tags are case-insensitive
+ "cmn", // ISO 639 language code
+ "cmn-Hans", // + script code
+ "CMN-hANS", // tags are case-insensitive
+ "cmn-hans-cn", // + ISO 3166-1 country code
+ "es-419", // + UN M.49 region code
+ "es-419-u-nu-latn-cu-bob", // + Unicode locale extension sequence
+ "cmn-hans-cn-t-ca-u-ca-x-t-u", // singleton subtags can also be used as private use subtags
+ "de-gregory-u-ca-gregory", // variant and extension subtags may be the same
+ "aa-a-foo-x-a-foo-bar", // variant subtags can also be used as private use subtags
+];
+
+testWithIntlConstructors(function (Constructor) {
+ validLanguageTags.forEach(function (tag) {
+ // this must not throw an exception for a valid language tag
+ var obj = new Constructor([tag]);
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/language-tags-with-underscore.js b/js/src/tests/test262/intl402/language-tags-with-underscore.js
new file mode 100644
index 0000000000..912b2827e5
--- /dev/null
+++ b/js/src/tests/test262/intl402/language-tags-with-underscore.js
@@ -0,0 +1,35 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 6.2.2_b
+description: Tests that language tags with "_" are not accepted.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var invalidLanguageTags = [
+ "de_DE",
+ "DE_de",
+ "cmn_Hans",
+ "cmn-hans_cn",
+ "es_419",
+ "es-419-u-nu-latn-cu_bob",
+ "i_klingon",
+ "cmn-hans-cn-t-ca-u-ca-x_t-u",
+ "enochian_enochian",
+ "de-gregory_u-ca-gregory",
+ "de-tester-Tester", // Case-insensitive duplicate variant subtag
+ "de-DE-u-kn-true-U-kn-true", // Case-insensitive duplicate singleton subtag
+];
+
+testWithIntlConstructors(function (Constructor) {
+ invalidLanguageTags.forEach(function (tag) {
+ // this must throw an exception for an invalid language tag
+ assert.throws(RangeError, function() {
+ var obj = new Constructor([tag]);
+ }, "Invalid language tag " + tag + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/shell.js b/js/src/tests/test262/intl402/shell.js
new file mode 100644
index 0000000000..33526c6378
--- /dev/null
+++ b/js/src/tests/test262/intl402/shell.js
@@ -0,0 +1,2717 @@
+// GENERATED, DO NOT EDIT
+// file: testIntl.js
+// Copyright (C) 2011 2012 Norbert Lindenberg. All rights reserved.
+// Copyright (C) 2012 2013 Mozilla Corporation. All rights reserved.
+// Copyright (C) 2020 Apple Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ This file contains shared functions for the tests in the conformance test
+ suite for the ECMAScript Internationalization API.
+author: Norbert Lindenberg
+defines:
+ - testWithIntlConstructors
+ - taintDataProperty
+ - taintMethod
+ - taintProperties
+ - taintArray
+ - getLocaleSupportInfo
+ - getInvalidLanguageTags
+ - isCanonicalizedStructurallyValidLanguageTag
+ - getInvalidLocaleArguments
+ - testOption
+ - testForUnwantedRegExpChanges
+ - allCalendars
+ - allCollations
+ - allNumberingSystems
+ - isValidNumberingSystem
+ - numberingSystemDigits
+ - allSimpleSanctionedUnits
+ - testNumberFormat
+ - getDateTimeComponents
+ - getDateTimeComponentValues
+ - isCanonicalizedStructurallyValidTimeZoneName
+ - partitionDurationFormatPattern
+ - formatDurationFormatPattern
+---*/
+/**
+ */
+
+
+/**
+ * @description Calls the provided function for every service constructor in
+ * the Intl object.
+ * @param {Function} f the function to call for each service constructor in
+ * the Intl object.
+ * @param {Function} Constructor the constructor object to test with.
+ */
+function testWithIntlConstructors(f) {
+ var constructors = ["Collator", "NumberFormat", "DateTimeFormat"];
+
+ // Optionally supported Intl constructors.
+ // NB: Intl.Locale isn't an Intl service constructor!
+ // Intl.DisplayNames cannot be called without type in options.
+ ["PluralRules", "RelativeTimeFormat", "ListFormat"].forEach(function(constructor) {
+ if (typeof Intl[constructor] === "function") {
+ constructors[constructors.length] = constructor;
+ }
+ });
+
+ constructors.forEach(function (constructor) {
+ var Constructor = Intl[constructor];
+ try {
+ f(Constructor);
+ } catch (e) {
+ e.message += " (Testing with " + constructor + ".)";
+ throw e;
+ }
+ });
+}
+
+
+/**
+ * Taints a named data property of the given object by installing
+ * a setter that throws an exception.
+ * @param {object} obj the object whose data property to taint
+ * @param {string} property the property to taint
+ */
+function taintDataProperty(obj, property) {
+ Object.defineProperty(obj, property, {
+ set: function(value) {
+ throw new Test262Error("Client code can adversely affect behavior: setter for " + property + ".");
+ },
+ enumerable: false,
+ configurable: true
+ });
+}
+
+
+/**
+ * Taints a named method of the given object by replacing it with a function
+ * that throws an exception.
+ * @param {object} obj the object whose method to taint
+ * @param {string} property the name of the method to taint
+ */
+function taintMethod(obj, property) {
+ Object.defineProperty(obj, property, {
+ value: function() {
+ throw new Test262Error("Client code can adversely affect behavior: method " + property + ".");
+ },
+ writable: true,
+ enumerable: false,
+ configurable: true
+ });
+}
+
+
+/**
+ * Taints the given properties (and similarly named properties) by installing
+ * setters on Object.prototype that throw exceptions.
+ * @param {Array} properties an array of property names to taint
+ */
+function taintProperties(properties) {
+ properties.forEach(function (property) {
+ var adaptedProperties = [property, "__" + property, "_" + property, property + "_", property + "__"];
+ adaptedProperties.forEach(function (property) {
+ taintDataProperty(Object.prototype, property);
+ });
+ });
+}
+
+
+/**
+ * Taints the Array object by creating a setter for the property "0" and
+ * replacing some key methods with functions that throw exceptions.
+ */
+function taintArray() {
+ taintDataProperty(Array.prototype, "0");
+ taintMethod(Array.prototype, "indexOf");
+ taintMethod(Array.prototype, "join");
+ taintMethod(Array.prototype, "push");
+ taintMethod(Array.prototype, "slice");
+ taintMethod(Array.prototype, "sort");
+}
+
+
+/**
+ * Gets locale support info for the given constructor object, which must be one
+ * of Intl constructors.
+ * @param {object} Constructor the constructor for which to get locale support info
+ * @param {object} options the options while calling the constructor
+ * @return {object} locale support info with the following properties:
+ * supported: array of fully supported language tags
+ * byFallback: array of language tags that are supported through fallbacks
+ * unsupported: array of unsupported language tags
+ */
+function getLocaleSupportInfo(Constructor, options) {
+ var languages = ["zh", "es", "en", "hi", "ur", "ar", "ja", "pa"];
+ var scripts = ["Latn", "Hans", "Deva", "Arab", "Jpan", "Hant", "Guru"];
+ var countries = ["CN", "IN", "US", "PK", "JP", "TW", "HK", "SG", "419"];
+
+ var allTags = [];
+ var i, j, k;
+ var language, script, country;
+ for (i = 0; i < languages.length; i++) {
+ language = languages[i];
+ allTags.push(language);
+ for (j = 0; j < scripts.length; j++) {
+ script = scripts[j];
+ allTags.push(language + "-" + script);
+ for (k = 0; k < countries.length; k++) {
+ country = countries[k];
+ allTags.push(language + "-" + script + "-" + country);
+ }
+ }
+ for (k = 0; k < countries.length; k++) {
+ country = countries[k];
+ allTags.push(language + "-" + country);
+ }
+ }
+
+ var supported = [];
+ var byFallback = [];
+ var unsupported = [];
+ for (i = 0; i < allTags.length; i++) {
+ var request = allTags[i];
+ var result = new Constructor([request], options).resolvedOptions().locale;
+ if (request === result) {
+ supported.push(request);
+ } else if (request.indexOf(result) === 0) {
+ byFallback.push(request);
+ } else {
+ unsupported.push(request);
+ }
+ }
+
+ return {
+ supported: supported,
+ byFallback: byFallback,
+ unsupported: unsupported
+ };
+}
+
+
+/**
+ * Returns an array of strings for which IsStructurallyValidLanguageTag() returns false
+ */
+function getInvalidLanguageTags() {
+ var invalidLanguageTags = [
+ "", // empty tag
+ "i", // singleton alone
+ "x", // private use without subtag
+ "u", // extension singleton in first place
+ "419", // region code in first place
+ "u-nu-latn-cu-bob", // extension sequence without language
+ "hans-cmn-cn", // "hans" could theoretically be a 4-letter language code,
+ // but those can't be followed by extlang codes.
+ "cmn-hans-cn-u-u", // duplicate singleton
+ "cmn-hans-cn-t-u-ca-u", // duplicate singleton
+ "de-gregory-gregory", // duplicate variant
+ "*", // language range
+ "de-*", // language range
+ "中文", // non-ASCII letters
+ "en-ß", // non-ASCII letters
+ "ıd", // non-ASCII letters
+ "es-Latn-latn", // two scripts
+ "pl-PL-pl", // two regions
+ "u-ca-gregory", // extension in first place
+ "de-1996-1996", // duplicate numeric variant
+ "pt-u-ca-gregory-u-nu-latn", // duplicate singleton subtag
+
+ // Invalid tags starting with: https://github.com/tc39/ecma402/pull/289
+ "no-nyn", // regular grandfathered in BCP47, but invalid in UTS35
+ "i-klingon", // irregular grandfathered in BCP47, but invalid in UTS35
+ "zh-hak-CN", // language with extlang in BCP47, but invalid in UTS35
+ "sgn-ils", // language with extlang in BCP47, but invalid in UTS35
+ "x-foo", // privateuse-only in BCP47, but invalid in UTS35
+ "x-en-US-12345", // more privateuse-only variants.
+ "x-12345-12345-en-US",
+ "x-en-US-12345-12345",
+ "x-en-u-foo",
+ "x-en-u-foo-u-bar",
+ "x-u-foo",
+
+ // underscores in different parts of the language tag
+ "de_DE",
+ "DE_de",
+ "cmn_Hans",
+ "cmn-hans_cn",
+ "es_419",
+ "es-419-u-nu-latn-cu_bob",
+ "i_klingon",
+ "cmn-hans-cn-t-ca-u-ca-x_t-u",
+ "enochian_enochian",
+ "de-gregory_u-ca-gregory",
+
+ "en\u0000", // null-terminator sequence
+ " en", // leading whitespace
+ "en ", // trailing whitespace
+ "it-IT-Latn", // country before script tag
+ "de-u", // incomplete Unicode extension sequences
+ "de-u-",
+ "de-u-ca-",
+ "de-u-ca-gregory-",
+ "si-x", // incomplete private-use tags
+ "x-",
+ "x-y-",
+ ];
+
+ // make sure the data above is correct
+ for (var i = 0; i < invalidLanguageTags.length; ++i) {
+ var invalidTag = invalidLanguageTags[i];
+ assert(
+ !isCanonicalizedStructurallyValidLanguageTag(invalidTag),
+ "Test data \"" + invalidTag + "\" is a canonicalized and structurally valid language tag."
+ );
+ }
+
+ return invalidLanguageTags;
+}
+
+
+/**
+ * @description Tests whether locale is a String value representing a
+ * structurally valid and canonicalized BCP 47 language tag, as defined in
+ * sections 6.2.2 and 6.2.3 of the ECMAScript Internationalization API
+ * Specification.
+ * @param {String} locale the string to be tested.
+ * @result {Boolean} whether the test succeeded.
+ */
+function isCanonicalizedStructurallyValidLanguageTag(locale) {
+
+ /**
+ * Regular expression defining Unicode BCP 47 Locale Identifiers.
+ *
+ * Spec: https://unicode.org/reports/tr35/#Unicode_locale_identifier
+ */
+ var alpha = "[a-z]",
+ digit = "[0-9]",
+ alphanum = "[a-z0-9]",
+ variant = "(" + alphanum + "{5,8}|(?:" + digit + alphanum + "{3}))",
+ region = "(" + alpha + "{2}|" + digit + "{3})",
+ script = "(" + alpha + "{4})",
+ language = "(" + alpha + "{2,3}|" + alpha + "{5,8})",
+ privateuse = "(x(-[a-z0-9]{1,8})+)",
+ singleton = "(" + digit + "|[a-wy-z])",
+ attribute= "(" + alphanum + "{3,8})",
+ keyword = "(" + alphanum + alpha + "(-" + alphanum + "{3,8})*)",
+ unicode_locale_extensions = "(u((-" + keyword + ")+|((-" + attribute + ")+(-" + keyword + ")*)))",
+ tlang = "(" + language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*)",
+ tfield = "(" + alpha + digit + "(-" + alphanum + "{3,8})+)",
+ transformed_extensions = "(t((-" + tlang + "(-" + tfield + ")*)|(-" + tfield + ")+))",
+ other_singleton = "(" + digit + "|[a-sv-wy-z])",
+ other_extensions = "(" + other_singleton + "(-" + alphanum + "{2,8})+)",
+ extension = "(" + unicode_locale_extensions + "|" + transformed_extensions + "|" + other_extensions + ")",
+ locale_id = language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*(-" + extension + ")*(-" + privateuse + ")?",
+ languageTag = "^(" + locale_id + ")$",
+ languageTagRE = new RegExp(languageTag, "i");
+
+ var duplicateSingleton = "-" + singleton + "-(.*-)?\\1(?!" + alphanum + ")",
+ duplicateSingletonRE = new RegExp(duplicateSingleton, "i"),
+ duplicateVariant = "(" + alphanum + "{2,8}-)+" + variant + "-(" + alphanum + "{2,8}-)*\\2(?!" + alphanum + ")",
+ duplicateVariantRE = new RegExp(duplicateVariant, "i");
+
+ var transformKeyRE = new RegExp("^" + alpha + digit + "$", "i");
+
+ /**
+ * Verifies that the given string is a well-formed Unicode BCP 47 Locale Identifier
+ * with no duplicate variant or singleton subtags.
+ *
+ * Spec: ECMAScript Internationalization API Specification, draft, 6.2.2.
+ */
+ function isStructurallyValidLanguageTag(locale) {
+ if (!languageTagRE.test(locale)) {
+ return false;
+ }
+ locale = locale.split(/-x-/)[0];
+ return !duplicateSingletonRE.test(locale) && !duplicateVariantRE.test(locale);
+ }
+
+
+ /**
+ * Mappings from complete tags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __tagMappings = {
+ // property names must be in lower case; values in canonical form
+
+ "art-lojban": "jbo",
+ "cel-gaulish": "xtg",
+ "zh-guoyu": "zh",
+ "zh-hakka": "hak",
+ "zh-xiang": "hsn",
+ };
+
+
+ /**
+ * Mappings from language subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __languageMappings = {
+ // property names and values must be in canonical case
+
+ "aam": "aas",
+ "aar": "aa",
+ "abk": "ab",
+ "adp": "dz",
+ "afr": "af",
+ "aju": "jrb",
+ "aka": "ak",
+ "alb": "sq",
+ "als": "sq",
+ "amh": "am",
+ "ara": "ar",
+ "arb": "ar",
+ "arg": "an",
+ "arm": "hy",
+ "asd": "snz",
+ "asm": "as",
+ "aue": "ktz",
+ "ava": "av",
+ "ave": "ae",
+ "aym": "ay",
+ "ayr": "ay",
+ "ayx": "nun",
+ "aze": "az",
+ "azj": "az",
+ "bak": "ba",
+ "bam": "bm",
+ "baq": "eu",
+ "bcc": "bal",
+ "bcl": "bik",
+ "bel": "be",
+ "ben": "bn",
+ "bgm": "bcg",
+ "bh": "bho",
+ "bih": "bho",
+ "bis": "bi",
+ "bjd": "drl",
+ "bod": "bo",
+ "bos": "bs",
+ "bre": "br",
+ "bul": "bg",
+ "bur": "my",
+ "bxk": "luy",
+ "bxr": "bua",
+ "cat": "ca",
+ "ccq": "rki",
+ "ces": "cs",
+ "cha": "ch",
+ "che": "ce",
+ "chi": "zh",
+ "chu": "cu",
+ "chv": "cv",
+ "cjr": "mom",
+ "cka": "cmr",
+ "cld": "syr",
+ "cmk": "xch",
+ "cmn": "zh",
+ "cor": "kw",
+ "cos": "co",
+ "coy": "pij",
+ "cqu": "quh",
+ "cre": "cr",
+ "cwd": "cr",
+ "cym": "cy",
+ "cze": "cs",
+ "dan": "da",
+ "deu": "de",
+ "dgo": "doi",
+ "dhd": "mwr",
+ "dik": "din",
+ "diq": "zza",
+ "dit": "dif",
+ "div": "dv",
+ "drh": "mn",
+ "dut": "nl",
+ "dzo": "dz",
+ "ekk": "et",
+ "ell": "el",
+ "emk": "man",
+ "eng": "en",
+ "epo": "eo",
+ "esk": "ik",
+ "est": "et",
+ "eus": "eu",
+ "ewe": "ee",
+ "fao": "fo",
+ "fas": "fa",
+ "fat": "ak",
+ "fij": "fj",
+ "fin": "fi",
+ "fra": "fr",
+ "fre": "fr",
+ "fry": "fy",
+ "fuc": "ff",
+ "ful": "ff",
+ "gav": "dev",
+ "gaz": "om",
+ "gbo": "grb",
+ "geo": "ka",
+ "ger": "de",
+ "gfx": "vaj",
+ "ggn": "gvr",
+ "gla": "gd",
+ "gle": "ga",
+ "glg": "gl",
+ "glv": "gv",
+ "gno": "gon",
+ "gre": "el",
+ "grn": "gn",
+ "gti": "nyc",
+ "gug": "gn",
+ "guj": "gu",
+ "guv": "duz",
+ "gya": "gba",
+ "hat": "ht",
+ "hau": "ha",
+ "hdn": "hai",
+ "hea": "hmn",
+ "heb": "he",
+ "her": "hz",
+ "him": "srx",
+ "hin": "hi",
+ "hmo": "ho",
+ "hrr": "jal",
+ "hrv": "hr",
+ "hun": "hu",
+ "hye": "hy",
+ "ibi": "opa",
+ "ibo": "ig",
+ "ice": "is",
+ "ido": "io",
+ "iii": "ii",
+ "ike": "iu",
+ "iku": "iu",
+ "ile": "ie",
+ "ilw": "gal",
+ "in": "id",
+ "ina": "ia",
+ "ind": "id",
+ "ipk": "ik",
+ "isl": "is",
+ "ita": "it",
+ "iw": "he",
+ "jav": "jv",
+ "jeg": "oyb",
+ "ji": "yi",
+ "jpn": "ja",
+ "jw": "jv",
+ "kal": "kl",
+ "kan": "kn",
+ "kas": "ks",
+ "kat": "ka",
+ "kau": "kr",
+ "kaz": "kk",
+ "kgc": "tdf",
+ "kgh": "kml",
+ "khk": "mn",
+ "khm": "km",
+ "kik": "ki",
+ "kin": "rw",
+ "kir": "ky",
+ "kmr": "ku",
+ "knc": "kr",
+ "kng": "kg",
+ "knn": "kok",
+ "koj": "kwv",
+ "kom": "kv",
+ "kon": "kg",
+ "kor": "ko",
+ "kpv": "kv",
+ "krm": "bmf",
+ "ktr": "dtp",
+ "kua": "kj",
+ "kur": "ku",
+ "kvs": "gdj",
+ "kwq": "yam",
+ "kxe": "tvd",
+ "kzj": "dtp",
+ "kzt": "dtp",
+ "lao": "lo",
+ "lat": "la",
+ "lav": "lv",
+ "lbk": "bnc",
+ "lii": "raq",
+ "lim": "li",
+ "lin": "ln",
+ "lit": "lt",
+ "llo": "ngt",
+ "lmm": "rmx",
+ "ltz": "lb",
+ "lub": "lu",
+ "lug": "lg",
+ "lvs": "lv",
+ "mac": "mk",
+ "mah": "mh",
+ "mal": "ml",
+ "mao": "mi",
+ "mar": "mr",
+ "may": "ms",
+ "meg": "cir",
+ "mhr": "chm",
+ "mkd": "mk",
+ "mlg": "mg",
+ "mlt": "mt",
+ "mnk": "man",
+ "mo": "ro",
+ "mol": "ro",
+ "mon": "mn",
+ "mri": "mi",
+ "msa": "ms",
+ "mst": "mry",
+ "mup": "raj",
+ "mwj": "vaj",
+ "mya": "my",
+ "myd": "aog",
+ "myt": "mry",
+ "nad": "xny",
+ "nau": "na",
+ "nav": "nv",
+ "nbl": "nr",
+ "ncp": "kdz",
+ "nde": "nd",
+ "ndo": "ng",
+ "nep": "ne",
+ "nld": "nl",
+ "nno": "nn",
+ "nns": "nbr",
+ "nnx": "ngv",
+ "no": "nb",
+ "nob": "nb",
+ "nor": "nb",
+ "npi": "ne",
+ "nts": "pij",
+ "nya": "ny",
+ "oci": "oc",
+ "ojg": "oj",
+ "oji": "oj",
+ "ori": "or",
+ "orm": "om",
+ "ory": "or",
+ "oss": "os",
+ "oun": "vaj",
+ "pan": "pa",
+ "pbu": "ps",
+ "pcr": "adx",
+ "per": "fa",
+ "pes": "fa",
+ "pli": "pi",
+ "plt": "mg",
+ "pmc": "huw",
+ "pmu": "phr",
+ "pnb": "lah",
+ "pol": "pl",
+ "por": "pt",
+ "ppa": "bfy",
+ "ppr": "lcq",
+ "pry": "prt",
+ "pus": "ps",
+ "puz": "pub",
+ "que": "qu",
+ "quz": "qu",
+ "rmy": "rom",
+ "roh": "rm",
+ "ron": "ro",
+ "rum": "ro",
+ "run": "rn",
+ "rus": "ru",
+ "sag": "sg",
+ "san": "sa",
+ "sca": "hle",
+ "scc": "sr",
+ "scr": "hr",
+ "sin": "si",
+ "skk": "oyb",
+ "slk": "sk",
+ "slo": "sk",
+ "slv": "sl",
+ "sme": "se",
+ "smo": "sm",
+ "sna": "sn",
+ "snd": "sd",
+ "som": "so",
+ "sot": "st",
+ "spa": "es",
+ "spy": "kln",
+ "sqi": "sq",
+ "src": "sc",
+ "srd": "sc",
+ "srp": "sr",
+ "ssw": "ss",
+ "sun": "su",
+ "swa": "sw",
+ "swe": "sv",
+ "swh": "sw",
+ "tah": "ty",
+ "tam": "ta",
+ "tat": "tt",
+ "tdu": "dtp",
+ "tel": "te",
+ "tgk": "tg",
+ "tgl": "fil",
+ "tha": "th",
+ "thc": "tpo",
+ "thx": "oyb",
+ "tib": "bo",
+ "tie": "ras",
+ "tir": "ti",
+ "tkk": "twm",
+ "tl": "fil",
+ "tlw": "weo",
+ "tmp": "tyj",
+ "tne": "kak",
+ "ton": "to",
+ "tsf": "taj",
+ "tsn": "tn",
+ "tso": "ts",
+ "ttq": "tmh",
+ "tuk": "tk",
+ "tur": "tr",
+ "tw": "ak",
+ "twi": "ak",
+ "uig": "ug",
+ "ukr": "uk",
+ "umu": "del",
+ "uok": "ema",
+ "urd": "ur",
+ "uzb": "uz",
+ "uzn": "uz",
+ "ven": "ve",
+ "vie": "vi",
+ "vol": "vo",
+ "wel": "cy",
+ "wln": "wa",
+ "wol": "wo",
+ "xba": "cax",
+ "xho": "xh",
+ "xia": "acn",
+ "xkh": "waw",
+ "xpe": "kpe",
+ "xsj": "suj",
+ "xsl": "den",
+ "ybd": "rki",
+ "ydd": "yi",
+ "yid": "yi",
+ "yma": "lrr",
+ "ymt": "mtm",
+ "yor": "yo",
+ "yos": "zom",
+ "yuu": "yug",
+ "zai": "zap",
+ "zha": "za",
+ "zho": "zh",
+ "zsm": "ms",
+ "zul": "zu",
+ "zyb": "za",
+ };
+
+
+ /**
+ * Mappings from region subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __regionMappings = {
+ // property names and values must be in canonical case
+
+ "004": "AF",
+ "008": "AL",
+ "010": "AQ",
+ "012": "DZ",
+ "016": "AS",
+ "020": "AD",
+ "024": "AO",
+ "028": "AG",
+ "031": "AZ",
+ "032": "AR",
+ "036": "AU",
+ "040": "AT",
+ "044": "BS",
+ "048": "BH",
+ "050": "BD",
+ "051": "AM",
+ "052": "BB",
+ "056": "BE",
+ "060": "BM",
+ "062": "034",
+ "064": "BT",
+ "068": "BO",
+ "070": "BA",
+ "072": "BW",
+ "074": "BV",
+ "076": "BR",
+ "084": "BZ",
+ "086": "IO",
+ "090": "SB",
+ "092": "VG",
+ "096": "BN",
+ "100": "BG",
+ "104": "MM",
+ "108": "BI",
+ "112": "BY",
+ "116": "KH",
+ "120": "CM",
+ "124": "CA",
+ "132": "CV",
+ "136": "KY",
+ "140": "CF",
+ "144": "LK",
+ "148": "TD",
+ "152": "CL",
+ "156": "CN",
+ "158": "TW",
+ "162": "CX",
+ "166": "CC",
+ "170": "CO",
+ "174": "KM",
+ "175": "YT",
+ "178": "CG",
+ "180": "CD",
+ "184": "CK",
+ "188": "CR",
+ "191": "HR",
+ "192": "CU",
+ "196": "CY",
+ "203": "CZ",
+ "204": "BJ",
+ "208": "DK",
+ "212": "DM",
+ "214": "DO",
+ "218": "EC",
+ "222": "SV",
+ "226": "GQ",
+ "230": "ET",
+ "231": "ET",
+ "232": "ER",
+ "233": "EE",
+ "234": "FO",
+ "238": "FK",
+ "239": "GS",
+ "242": "FJ",
+ "246": "FI",
+ "248": "AX",
+ "249": "FR",
+ "250": "FR",
+ "254": "GF",
+ "258": "PF",
+ "260": "TF",
+ "262": "DJ",
+ "266": "GA",
+ "268": "GE",
+ "270": "GM",
+ "275": "PS",
+ "276": "DE",
+ "278": "DE",
+ "280": "DE",
+ "288": "GH",
+ "292": "GI",
+ "296": "KI",
+ "300": "GR",
+ "304": "GL",
+ "308": "GD",
+ "312": "GP",
+ "316": "GU",
+ "320": "GT",
+ "324": "GN",
+ "328": "GY",
+ "332": "HT",
+ "334": "HM",
+ "336": "VA",
+ "340": "HN",
+ "344": "HK",
+ "348": "HU",
+ "352": "IS",
+ "356": "IN",
+ "360": "ID",
+ "364": "IR",
+ "368": "IQ",
+ "372": "IE",
+ "376": "IL",
+ "380": "IT",
+ "384": "CI",
+ "388": "JM",
+ "392": "JP",
+ "398": "KZ",
+ "400": "JO",
+ "404": "KE",
+ "408": "KP",
+ "410": "KR",
+ "414": "KW",
+ "417": "KG",
+ "418": "LA",
+ "422": "LB",
+ "426": "LS",
+ "428": "LV",
+ "430": "LR",
+ "434": "LY",
+ "438": "LI",
+ "440": "LT",
+ "442": "LU",
+ "446": "MO",
+ "450": "MG",
+ "454": "MW",
+ "458": "MY",
+ "462": "MV",
+ "466": "ML",
+ "470": "MT",
+ "474": "MQ",
+ "478": "MR",
+ "480": "MU",
+ "484": "MX",
+ "492": "MC",
+ "496": "MN",
+ "498": "MD",
+ "499": "ME",
+ "500": "MS",
+ "504": "MA",
+ "508": "MZ",
+ "512": "OM",
+ "516": "NA",
+ "520": "NR",
+ "524": "NP",
+ "528": "NL",
+ "531": "CW",
+ "533": "AW",
+ "534": "SX",
+ "535": "BQ",
+ "540": "NC",
+ "548": "VU",
+ "554": "NZ",
+ "558": "NI",
+ "562": "NE",
+ "566": "NG",
+ "570": "NU",
+ "574": "NF",
+ "578": "NO",
+ "580": "MP",
+ "581": "UM",
+ "583": "FM",
+ "584": "MH",
+ "585": "PW",
+ "586": "PK",
+ "591": "PA",
+ "598": "PG",
+ "600": "PY",
+ "604": "PE",
+ "608": "PH",
+ "612": "PN",
+ "616": "PL",
+ "620": "PT",
+ "624": "GW",
+ "626": "TL",
+ "630": "PR",
+ "634": "QA",
+ "638": "RE",
+ "642": "RO",
+ "643": "RU",
+ "646": "RW",
+ "652": "BL",
+ "654": "SH",
+ "659": "KN",
+ "660": "AI",
+ "662": "LC",
+ "663": "MF",
+ "666": "PM",
+ "670": "VC",
+ "674": "SM",
+ "678": "ST",
+ "682": "SA",
+ "686": "SN",
+ "688": "RS",
+ "690": "SC",
+ "694": "SL",
+ "702": "SG",
+ "703": "SK",
+ "704": "VN",
+ "705": "SI",
+ "706": "SO",
+ "710": "ZA",
+ "716": "ZW",
+ "720": "YE",
+ "724": "ES",
+ "728": "SS",
+ "729": "SD",
+ "732": "EH",
+ "736": "SD",
+ "740": "SR",
+ "744": "SJ",
+ "748": "SZ",
+ "752": "SE",
+ "756": "CH",
+ "760": "SY",
+ "762": "TJ",
+ "764": "TH",
+ "768": "TG",
+ "772": "TK",
+ "776": "TO",
+ "780": "TT",
+ "784": "AE",
+ "788": "TN",
+ "792": "TR",
+ "795": "TM",
+ "796": "TC",
+ "798": "TV",
+ "800": "UG",
+ "804": "UA",
+ "807": "MK",
+ "818": "EG",
+ "826": "GB",
+ "830": "JE",
+ "831": "GG",
+ "832": "JE",
+ "833": "IM",
+ "834": "TZ",
+ "840": "US",
+ "850": "VI",
+ "854": "BF",
+ "858": "UY",
+ "860": "UZ",
+ "862": "VE",
+ "876": "WF",
+ "882": "WS",
+ "886": "YE",
+ "887": "YE",
+ "891": "RS",
+ "894": "ZM",
+ "958": "AA",
+ "959": "QM",
+ "960": "QN",
+ "962": "QP",
+ "963": "QQ",
+ "964": "QR",
+ "965": "QS",
+ "966": "QT",
+ "967": "EU",
+ "968": "QV",
+ "969": "QW",
+ "970": "QX",
+ "971": "QY",
+ "972": "QZ",
+ "973": "XA",
+ "974": "XB",
+ "975": "XC",
+ "976": "XD",
+ "977": "XE",
+ "978": "XF",
+ "979": "XG",
+ "980": "XH",
+ "981": "XI",
+ "982": "XJ",
+ "983": "XK",
+ "984": "XL",
+ "985": "XM",
+ "986": "XN",
+ "987": "XO",
+ "988": "XP",
+ "989": "XQ",
+ "990": "XR",
+ "991": "XS",
+ "992": "XT",
+ "993": "XU",
+ "994": "XV",
+ "995": "XW",
+ "996": "XX",
+ "997": "XY",
+ "998": "XZ",
+ "999": "ZZ",
+ "BU": "MM",
+ "CS": "RS",
+ "CT": "KI",
+ "DD": "DE",
+ "DY": "BJ",
+ "FQ": "AQ",
+ "FX": "FR",
+ "HV": "BF",
+ "JT": "UM",
+ "MI": "UM",
+ "NH": "VU",
+ "NQ": "AQ",
+ "PU": "UM",
+ "PZ": "PA",
+ "QU": "EU",
+ "RH": "ZW",
+ "TP": "TL",
+ "UK": "GB",
+ "VD": "VN",
+ "WK": "UM",
+ "YD": "YE",
+ "YU": "RS",
+ "ZR": "CD",
+ };
+
+
+ /**
+ * Complex mappings from language subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __complexLanguageMappings = {
+ // property names and values must be in canonical case
+
+ "cnr": {language: "sr", region: "ME"},
+ "drw": {language: "fa", region: "AF"},
+ "hbs": {language: "sr", script: "Latn"},
+ "prs": {language: "fa", region: "AF"},
+ "sh": {language: "sr", script: "Latn"},
+ "swc": {language: "sw", region: "CD"},
+ "tnf": {language: "fa", region: "AF"},
+ };
+
+
+ /**
+ * Complex mappings from region subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __complexRegionMappings = {
+ // property names and values must be in canonical case
+
+ "172": {
+ default: "RU",
+ "ab": "GE",
+ "az": "AZ",
+ "be": "BY",
+ "crh": "UA",
+ "gag": "MD",
+ "got": "UA",
+ "hy": "AM",
+ "ji": "UA",
+ "ka": "GE",
+ "kaa": "UZ",
+ "kk": "KZ",
+ "ku-Yezi": "GE",
+ "ky": "KG",
+ "os": "GE",
+ "rue": "UA",
+ "sog": "UZ",
+ "tg": "TJ",
+ "tk": "TM",
+ "tkr": "AZ",
+ "tly": "AZ",
+ "ttt": "AZ",
+ "ug-Cyrl": "KZ",
+ "uk": "UA",
+ "und-Armn": "AM",
+ "und-Chrs": "UZ",
+ "und-Geor": "GE",
+ "und-Goth": "UA",
+ "und-Sogd": "UZ",
+ "und-Sogo": "UZ",
+ "und-Yezi": "GE",
+ "uz": "UZ",
+ "xco": "UZ",
+ "xmf": "GE",
+ },
+ "200": {
+ default: "CZ",
+ "sk": "SK",
+ },
+ "530": {
+ default: "CW",
+ "vic": "SX",
+ },
+ "532": {
+ default: "CW",
+ "vic": "SX",
+ },
+ "536": {
+ default: "SA",
+ "akk": "IQ",
+ "ckb": "IQ",
+ "ku-Arab": "IQ",
+ "mis": "IQ",
+ "syr": "IQ",
+ "und-Hatr": "IQ",
+ "und-Syrc": "IQ",
+ "und-Xsux": "IQ",
+ },
+ "582": {
+ default: "FM",
+ "mh": "MH",
+ "pau": "PW",
+ },
+ "810": {
+ default: "RU",
+ "ab": "GE",
+ "az": "AZ",
+ "be": "BY",
+ "crh": "UA",
+ "et": "EE",
+ "gag": "MD",
+ "got": "UA",
+ "hy": "AM",
+ "ji": "UA",
+ "ka": "GE",
+ "kaa": "UZ",
+ "kk": "KZ",
+ "ku-Yezi": "GE",
+ "ky": "KG",
+ "lt": "LT",
+ "ltg": "LV",
+ "lv": "LV",
+ "os": "GE",
+ "rue": "UA",
+ "sgs": "LT",
+ "sog": "UZ",
+ "tg": "TJ",
+ "tk": "TM",
+ "tkr": "AZ",
+ "tly": "AZ",
+ "ttt": "AZ",
+ "ug-Cyrl": "KZ",
+ "uk": "UA",
+ "und-Armn": "AM",
+ "und-Chrs": "UZ",
+ "und-Geor": "GE",
+ "und-Goth": "UA",
+ "und-Sogd": "UZ",
+ "und-Sogo": "UZ",
+ "und-Yezi": "GE",
+ "uz": "UZ",
+ "vro": "EE",
+ "xco": "UZ",
+ "xmf": "GE",
+ },
+ "890": {
+ default: "RS",
+ "bs": "BA",
+ "hr": "HR",
+ "mk": "MK",
+ "sl": "SI",
+ },
+ "AN": {
+ default: "CW",
+ "vic": "SX",
+ },
+ "NT": {
+ default: "SA",
+ "akk": "IQ",
+ "ckb": "IQ",
+ "ku-Arab": "IQ",
+ "mis": "IQ",
+ "syr": "IQ",
+ "und-Hatr": "IQ",
+ "und-Syrc": "IQ",
+ "und-Xsux": "IQ",
+ },
+ "PC": {
+ default: "FM",
+ "mh": "MH",
+ "pau": "PW",
+ },
+ "SU": {
+ default: "RU",
+ "ab": "GE",
+ "az": "AZ",
+ "be": "BY",
+ "crh": "UA",
+ "et": "EE",
+ "gag": "MD",
+ "got": "UA",
+ "hy": "AM",
+ "ji": "UA",
+ "ka": "GE",
+ "kaa": "UZ",
+ "kk": "KZ",
+ "ku-Yezi": "GE",
+ "ky": "KG",
+ "lt": "LT",
+ "ltg": "LV",
+ "lv": "LV",
+ "os": "GE",
+ "rue": "UA",
+ "sgs": "LT",
+ "sog": "UZ",
+ "tg": "TJ",
+ "tk": "TM",
+ "tkr": "AZ",
+ "tly": "AZ",
+ "ttt": "AZ",
+ "ug-Cyrl": "KZ",
+ "uk": "UA",
+ "und-Armn": "AM",
+ "und-Chrs": "UZ",
+ "und-Geor": "GE",
+ "und-Goth": "UA",
+ "und-Sogd": "UZ",
+ "und-Sogo": "UZ",
+ "und-Yezi": "GE",
+ "uz": "UZ",
+ "vro": "EE",
+ "xco": "UZ",
+ "xmf": "GE",
+ },
+ };
+
+
+ /**
+ * Mappings from variant subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __variantMappings = {
+ // property names and values must be in canonical case
+
+ "aaland": {type: "region", replacement: "AX"},
+ "arevela": {type: "language", replacement: "hy"},
+ "arevmda": {type: "language", replacement: "hyw"},
+ "heploc": {type: "variant", replacement: "alalc97"},
+ "polytoni": {type: "variant", replacement: "polyton"},
+ };
+
+
+ /**
+ * Mappings from Unicode extension subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __unicodeMappings = {
+ // property names and values must be in canonical case
+
+ "ca": {
+ "ethiopic-amete-alem": "ethioaa",
+ "islamicc": "islamic-civil",
+ },
+ "kb": {
+ "yes": "true",
+ },
+ "kc": {
+ "yes": "true",
+ },
+ "kh": {
+ "yes": "true",
+ },
+ "kk": {
+ "yes": "true",
+ },
+ "kn": {
+ "yes": "true",
+ },
+ "ks": {
+ "primary": "level1",
+ "tertiary": "level3",
+ },
+ "ms": {
+ "imperial": "uksystem",
+ },
+ "rg": {
+ "cn11": "cnbj",
+ "cn12": "cntj",
+ "cn13": "cnhe",
+ "cn14": "cnsx",
+ "cn15": "cnmn",
+ "cn21": "cnln",
+ "cn22": "cnjl",
+ "cn23": "cnhl",
+ "cn31": "cnsh",
+ "cn32": "cnjs",
+ "cn33": "cnzj",
+ "cn34": "cnah",
+ "cn35": "cnfj",
+ "cn36": "cnjx",
+ "cn37": "cnsd",
+ "cn41": "cnha",
+ "cn42": "cnhb",
+ "cn43": "cnhn",
+ "cn44": "cngd",
+ "cn45": "cngx",
+ "cn46": "cnhi",
+ "cn50": "cncq",
+ "cn51": "cnsc",
+ "cn52": "cngz",
+ "cn53": "cnyn",
+ "cn54": "cnxz",
+ "cn61": "cnsn",
+ "cn62": "cngs",
+ "cn63": "cnqh",
+ "cn64": "cnnx",
+ "cn65": "cnxj",
+ "cz10a": "cz110",
+ "cz10b": "cz111",
+ "cz10c": "cz112",
+ "cz10d": "cz113",
+ "cz10e": "cz114",
+ "cz10f": "cz115",
+ "cz611": "cz663",
+ "cz612": "cz632",
+ "cz613": "cz633",
+ "cz614": "cz634",
+ "cz615": "cz635",
+ "cz621": "cz641",
+ "cz622": "cz642",
+ "cz623": "cz643",
+ "cz624": "cz644",
+ "cz626": "cz646",
+ "cz627": "cz647",
+ "czjc": "cz31",
+ "czjm": "cz64",
+ "czka": "cz41",
+ "czkr": "cz52",
+ "czli": "cz51",
+ "czmo": "cz80",
+ "czol": "cz71",
+ "czpa": "cz53",
+ "czpl": "cz32",
+ "czpr": "cz10",
+ "czst": "cz20",
+ "czus": "cz42",
+ "czvy": "cz63",
+ "czzl": "cz72",
+ "fra": "frges",
+ "frb": "frnaq",
+ "frc": "frara",
+ "frd": "frbfc",
+ "fre": "frbre",
+ "frf": "frcvl",
+ "frg": "frges",
+ "frh": "frcor",
+ "fri": "frbfc",
+ "frj": "fridf",
+ "frk": "frocc",
+ "frl": "frnaq",
+ "frm": "frges",
+ "frn": "frocc",
+ "fro": "frhdf",
+ "frp": "frnor",
+ "frq": "frnor",
+ "frr": "frpdl",
+ "frs": "frhdf",
+ "frt": "frnaq",
+ "fru": "frpac",
+ "frv": "frara",
+ "laxn": "laxs",
+ "lud": "lucl",
+ "lug": "luec",
+ "lul": "luca",
+ "mrnkc": "mr13",
+ "no23": "no50",
+ "nzn": "nzauk",
+ "nzs": "nzcan",
+ "omba": "ombj",
+ "omsh": "omsj",
+ "plds": "pl02",
+ "plkp": "pl04",
+ "pllb": "pl08",
+ "plld": "pl10",
+ "pllu": "pl06",
+ "plma": "pl12",
+ "plmz": "pl14",
+ "plop": "pl16",
+ "plpd": "pl20",
+ "plpk": "pl18",
+ "plpm": "pl22",
+ "plsk": "pl26",
+ "plsl": "pl24",
+ "plwn": "pl28",
+ "plwp": "pl30",
+ "plzp": "pl32",
+ "tteto": "tttob",
+ "ttrcm": "ttmrc",
+ "ttwto": "tttob",
+ "twkhq": "twkhh",
+ "twtnq": "twtnn",
+ "twtpq": "twnwt",
+ "twtxq": "twtxg",
+ },
+ "sd": {
+ "cn11": "cnbj",
+ "cn12": "cntj",
+ "cn13": "cnhe",
+ "cn14": "cnsx",
+ "cn15": "cnmn",
+ "cn21": "cnln",
+ "cn22": "cnjl",
+ "cn23": "cnhl",
+ "cn31": "cnsh",
+ "cn32": "cnjs",
+ "cn33": "cnzj",
+ "cn34": "cnah",
+ "cn35": "cnfj",
+ "cn36": "cnjx",
+ "cn37": "cnsd",
+ "cn41": "cnha",
+ "cn42": "cnhb",
+ "cn43": "cnhn",
+ "cn44": "cngd",
+ "cn45": "cngx",
+ "cn46": "cnhi",
+ "cn50": "cncq",
+ "cn51": "cnsc",
+ "cn52": "cngz",
+ "cn53": "cnyn",
+ "cn54": "cnxz",
+ "cn61": "cnsn",
+ "cn62": "cngs",
+ "cn63": "cnqh",
+ "cn64": "cnnx",
+ "cn65": "cnxj",
+ "cz10a": "cz110",
+ "cz10b": "cz111",
+ "cz10c": "cz112",
+ "cz10d": "cz113",
+ "cz10e": "cz114",
+ "cz10f": "cz115",
+ "cz611": "cz663",
+ "cz612": "cz632",
+ "cz613": "cz633",
+ "cz614": "cz634",
+ "cz615": "cz635",
+ "cz621": "cz641",
+ "cz622": "cz642",
+ "cz623": "cz643",
+ "cz624": "cz644",
+ "cz626": "cz646",
+ "cz627": "cz647",
+ "czjc": "cz31",
+ "czjm": "cz64",
+ "czka": "cz41",
+ "czkr": "cz52",
+ "czli": "cz51",
+ "czmo": "cz80",
+ "czol": "cz71",
+ "czpa": "cz53",
+ "czpl": "cz32",
+ "czpr": "cz10",
+ "czst": "cz20",
+ "czus": "cz42",
+ "czvy": "cz63",
+ "czzl": "cz72",
+ "fra": "frges",
+ "frb": "frnaq",
+ "frc": "frara",
+ "frd": "frbfc",
+ "fre": "frbre",
+ "frf": "frcvl",
+ "frg": "frges",
+ "frh": "frcor",
+ "fri": "frbfc",
+ "frj": "fridf",
+ "frk": "frocc",
+ "frl": "frnaq",
+ "frm": "frges",
+ "frn": "frocc",
+ "fro": "frhdf",
+ "frp": "frnor",
+ "frq": "frnor",
+ "frr": "frpdl",
+ "frs": "frhdf",
+ "frt": "frnaq",
+ "fru": "frpac",
+ "frv": "frara",
+ "laxn": "laxs",
+ "lud": "lucl",
+ "lug": "luec",
+ "lul": "luca",
+ "mrnkc": "mr13",
+ "no23": "no50",
+ "nzn": "nzauk",
+ "nzs": "nzcan",
+ "omba": "ombj",
+ "omsh": "omsj",
+ "plds": "pl02",
+ "plkp": "pl04",
+ "pllb": "pl08",
+ "plld": "pl10",
+ "pllu": "pl06",
+ "plma": "pl12",
+ "plmz": "pl14",
+ "plop": "pl16",
+ "plpd": "pl20",
+ "plpk": "pl18",
+ "plpm": "pl22",
+ "plsk": "pl26",
+ "plsl": "pl24",
+ "plwn": "pl28",
+ "plwp": "pl30",
+ "plzp": "pl32",
+ "tteto": "tttob",
+ "ttrcm": "ttmrc",
+ "ttwto": "tttob",
+ "twkhq": "twkhh",
+ "twtnq": "twtnn",
+ "twtpq": "twnwt",
+ "twtxq": "twtxg",
+ },
+ "tz": {
+ "aqams": "nzakl",
+ "cnckg": "cnsha",
+ "cnhrb": "cnsha",
+ "cnkhg": "cnurc",
+ "cuba": "cuhav",
+ "egypt": "egcai",
+ "eire": "iedub",
+ "est": "utcw05",
+ "gmt0": "gmt",
+ "hongkong": "hkhkg",
+ "hst": "utcw10",
+ "iceland": "isrey",
+ "iran": "irthr",
+ "israel": "jeruslm",
+ "jamaica": "jmkin",
+ "japan": "jptyo",
+ "libya": "lytip",
+ "mst": "utcw07",
+ "navajo": "usden",
+ "poland": "plwaw",
+ "portugal": "ptlis",
+ "prc": "cnsha",
+ "roc": "twtpe",
+ "rok": "krsel",
+ "turkey": "trist",
+ "uct": "utc",
+ "usnavajo": "usden",
+ "zulu": "utc",
+ },
+ };
+
+
+ /**
+ * Mappings from Unicode extension subtags to preferred values.
+ *
+ * Spec: http://unicode.org/reports/tr35/#Identifiers
+ * Version: CLDR, version 36.1
+ */
+ var __transformMappings = {
+ // property names and values must be in canonical case
+
+ "d0": {
+ "name": "charname",
+ },
+ "m0": {
+ "names": "prprname",
+ },
+ };
+
+ /**
+ * Canonicalizes the given well-formed BCP 47 language tag, including regularized case of subtags.
+ *
+ * Spec: ECMAScript Internationalization API Specification, draft, 6.2.3.
+ * Spec: RFC 5646, section 4.5.
+ */
+ function canonicalizeLanguageTag(locale) {
+
+ // start with lower case for easier processing, and because most subtags will need to be lower case anyway
+ locale = locale.toLowerCase();
+
+ // handle mappings for complete tags
+ if (__tagMappings.hasOwnProperty(locale)) {
+ return __tagMappings[locale];
+ }
+
+ var subtags = locale.split("-");
+ var i = 0;
+
+ // handle standard part: all subtags before first variant or singleton subtag
+ var language;
+ var script;
+ var region;
+ while (i < subtags.length) {
+ var subtag = subtags[i];
+ if (i === 0) {
+ language = subtag;
+ } else if (subtag.length === 2 || subtag.length === 3) {
+ region = subtag.toUpperCase();
+ } else if (subtag.length === 4 && !("0" <= subtag[0] && subtag[0] <= "9")) {
+ script = subtag[0].toUpperCase() + subtag.substring(1).toLowerCase();
+ } else {
+ break;
+ }
+ i++;
+ }
+
+ if (__languageMappings.hasOwnProperty(language)) {
+ language = __languageMappings[language];
+ } else if (__complexLanguageMappings.hasOwnProperty(language)) {
+ var mapping = __complexLanguageMappings[language];
+
+ language = mapping.language;
+ if (script === undefined && mapping.hasOwnProperty("script")) {
+ script = mapping.script;
+ }
+ if (region === undefined && mapping.hasOwnProperty("region")) {
+ region = mapping.region;
+ }
+ }
+
+ if (region !== undefined) {
+ if (__regionMappings.hasOwnProperty(region)) {
+ region = __regionMappings[region];
+ } else if (__complexRegionMappings.hasOwnProperty(region)) {
+ var mapping = __complexRegionMappings[region];
+
+ var mappingKey = language;
+ if (script !== undefined) {
+ mappingKey += "-" + script;
+ }
+
+ if (mapping.hasOwnProperty(mappingKey)) {
+ region = mapping[mappingKey];
+ } else {
+ region = mapping.default;
+ }
+ }
+ }
+
+ // handle variants
+ var variants = [];
+ while (i < subtags.length && subtags[i].length > 1) {
+ var variant = subtags[i];
+
+ if (__variantMappings.hasOwnProperty(variant)) {
+ var mapping = __variantMappings[variant];
+ switch (mapping.type) {
+ case "language":
+ language = mapping.replacement;
+ break;
+
+ case "region":
+ region = mapping.replacement;
+ break;
+
+ case "variant":
+ variants.push(mapping.replacement);
+ break;
+
+ default:
+ throw new Error("illegal variant mapping type");
+ }
+ } else {
+ variants.push(variant);
+ }
+
+ i += 1;
+ }
+ variants.sort();
+
+ // handle extensions
+ var extensions = [];
+ while (i < subtags.length && subtags[i] !== "x") {
+ var extensionStart = i;
+ i++;
+ while (i < subtags.length && subtags[i].length > 1) {
+ i++;
+ }
+
+ var extension;
+ var extensionKey = subtags[extensionStart];
+ if (extensionKey === "u") {
+ var j = extensionStart + 1;
+
+ // skip over leading attributes
+ while (j < i && subtags[j].length > 2) {
+ j++;
+ }
+
+ extension = subtags.slice(extensionStart, j).join("-");
+
+ while (j < i) {
+ var keyStart = j;
+ j++;
+
+ while (j < i && subtags[j].length > 2) {
+ j++;
+ }
+
+ var key = subtags[keyStart];
+ var value = subtags.slice(keyStart + 1, j).join("-");
+
+ if (__unicodeMappings.hasOwnProperty(key)) {
+ var mapping = __unicodeMappings[key];
+ if (mapping.hasOwnProperty(value)) {
+ value = mapping[value];
+ }
+ }
+
+ extension += "-" + key;
+ if (value !== "" && value !== "true") {
+ extension += "-" + value;
+ }
+ }
+ } else if (extensionKey === "t") {
+ var j = extensionStart + 1;
+
+ while (j < i && !transformKeyRE.test(subtags[j])) {
+ j++;
+ }
+
+ extension = "t";
+
+ var transformLanguage = subtags.slice(extensionStart + 1, j).join("-");
+ if (transformLanguage !== "") {
+ extension += "-" + canonicalizeLanguageTag(transformLanguage).toLowerCase();
+ }
+
+ while (j < i) {
+ var keyStart = j;
+ j++;
+
+ while (j < i && subtags[j].length > 2) {
+ j++;
+ }
+
+ var key = subtags[keyStart];
+ var value = subtags.slice(keyStart + 1, j).join("-");
+
+ if (__transformMappings.hasOwnProperty(key)) {
+ var mapping = __transformMappings[key];
+ if (mapping.hasOwnProperty(value)) {
+ value = mapping[value];
+ }
+ }
+
+ extension += "-" + key + "-" + value;
+ }
+ } else {
+ extension = subtags.slice(extensionStart, i).join("-");
+ }
+
+ extensions.push(extension);
+ }
+ extensions.sort();
+
+ // handle private use
+ var privateUse;
+ if (i < subtags.length) {
+ privateUse = subtags.slice(i).join("-");
+ }
+
+ // put everything back together
+ var canonical = language;
+ if (script !== undefined) {
+ canonical += "-" + script;
+ }
+ if (region !== undefined) {
+ canonical += "-" + region;
+ }
+ if (variants.length > 0) {
+ canonical += "-" + variants.join("-");
+ }
+ if (extensions.length > 0) {
+ canonical += "-" + extensions.join("-");
+ }
+ if (privateUse !== undefined) {
+ if (canonical.length > 0) {
+ canonical += "-" + privateUse;
+ } else {
+ canonical = privateUse;
+ }
+ }
+
+ return canonical;
+ }
+
+ return typeof locale === "string" && isStructurallyValidLanguageTag(locale) &&
+ canonicalizeLanguageTag(locale) === locale;
+}
+
+
+/**
+ * Returns an array of error cases handled by CanonicalizeLocaleList().
+ */
+function getInvalidLocaleArguments() {
+ function CustomError() {}
+
+ var topLevelErrors = [
+ // fails ToObject
+ [null, TypeError],
+
+ // fails Get
+ [{ get length() { throw new CustomError(); } }, CustomError],
+
+ // fail ToLength
+ [{ length: Symbol.toPrimitive }, TypeError],
+ [{ length: { get [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError],
+ [{ length: { [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError],
+ [{ length: { get valueOf() { throw new CustomError(); } } }, CustomError],
+ [{ length: { valueOf() { throw new CustomError(); } } }, CustomError],
+ [{ length: { get toString() { throw new CustomError(); } } }, CustomError],
+ [{ length: { toString() { throw new CustomError(); } } }, CustomError],
+
+ // fail type check
+ [[undefined], TypeError],
+ [[null], TypeError],
+ [[true], TypeError],
+ [[Symbol.toPrimitive], TypeError],
+ [[1], TypeError],
+ [[0.1], TypeError],
+ [[NaN], TypeError],
+ ];
+
+ var invalidLanguageTags = [
+ "", // empty tag
+ "i", // singleton alone
+ "x", // private use without subtag
+ "u", // extension singleton in first place
+ "419", // region code in first place
+ "u-nu-latn-cu-bob", // extension sequence without language
+ "hans-cmn-cn", // "hans" could theoretically be a 4-letter language code,
+ // but those can't be followed by extlang codes.
+ "abcdefghi", // overlong language
+ "cmn-hans-cn-u-u", // duplicate singleton
+ "cmn-hans-cn-t-u-ca-u", // duplicate singleton
+ "de-gregory-gregory", // duplicate variant
+ "*", // language range
+ "de-*", // language range
+ "中文", // non-ASCII letters
+ "en-ß", // non-ASCII letters
+ "ıd" // non-ASCII letters
+ ];
+
+ return topLevelErrors.concat(
+ invalidLanguageTags.map(tag => [tag, RangeError]),
+ invalidLanguageTags.map(tag => [[tag], RangeError]),
+ invalidLanguageTags.map(tag => [["en", tag], RangeError]),
+ )
+}
+
+/**
+ * Tests whether the named options property is correctly handled by the given constructor.
+ * @param {object} Constructor the constructor to test.
+ * @param {string} property the name of the options property to test.
+ * @param {string} type the type that values of the property are expected to have
+ * @param {Array} [values] an array of allowed values for the property. Not needed for boolean.
+ * @param {any} fallback the fallback value that the property assumes if not provided.
+ * @param {object} testOptions additional options:
+ * @param {boolean} isOptional whether support for this property is optional for implementations.
+ * @param {boolean} noReturn whether the resulting value of the property is not returned.
+ * @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent.
+ * @param {object} extra additional option to pass along, properties are value -> {option: value}.
+ */
+function testOption(Constructor, property, type, values, fallback, testOptions) {
+ var isOptional = testOptions !== undefined && testOptions.isOptional === true;
+ var noReturn = testOptions !== undefined && testOptions.noReturn === true;
+ var isILD = testOptions !== undefined && testOptions.isILD === true;
+
+ function addExtraOptions(options, value, testOptions) {
+ if (testOptions !== undefined && testOptions.extra !== undefined) {
+ var extra;
+ if (value !== undefined && testOptions.extra[value] !== undefined) {
+ extra = testOptions.extra[value];
+ } else if (testOptions.extra.any !== undefined) {
+ extra = testOptions.extra.any;
+ }
+ if (extra !== undefined) {
+ Object.getOwnPropertyNames(extra).forEach(function (prop) {
+ options[prop] = extra[prop];
+ });
+ }
+ }
+ }
+
+ var testValues, options, obj, expected, actual, error;
+
+ // test that the specified values are accepted. Also add values that convert to specified values.
+ if (type === "boolean") {
+ if (values === undefined) {
+ values = [true, false];
+ }
+ testValues = values.slice(0);
+ testValues.push(888);
+ testValues.push(0);
+ } else if (type === "string") {
+ testValues = values.slice(0);
+ testValues.push({toString: function () { return values[0]; }});
+ }
+ testValues.forEach(function (value) {
+ options = {};
+ options[property] = value;
+ addExtraOptions(options, value, testOptions);
+ obj = new Constructor(undefined, options);
+ if (noReturn) {
+ if (obj.resolvedOptions().hasOwnProperty(property)) {
+ throw new Test262Error("Option property " + property + " is returned, but shouldn't be.");
+ }
+ } else {
+ actual = obj.resolvedOptions()[property];
+ if (isILD) {
+ if (actual !== undefined && values.indexOf(actual) === -1) {
+ throw new Test262Error("Invalid value " + actual + " returned for property " + property + ".");
+ }
+ } else {
+ if (type === "boolean") {
+ expected = Boolean(value);
+ } else if (type === "string") {
+ expected = String(value);
+ }
+ if (actual !== expected && !(isOptional && actual === undefined)) {
+ throw new Test262Error("Option value " + value + " for property " + property +
+ " was not accepted; got " + actual + " instead.");
+ }
+ }
+ }
+ });
+
+ // test that invalid values are rejected
+ if (type === "string") {
+ var invalidValues = ["invalidValue", -1, null];
+ // assume that we won't have values in caseless scripts
+ if (values[0].toUpperCase() !== values[0]) {
+ invalidValues.push(values[0].toUpperCase());
+ } else {
+ invalidValues.push(values[0].toLowerCase());
+ }
+ invalidValues.forEach(function (value) {
+ options = {};
+ options[property] = value;
+ addExtraOptions(options, value, testOptions);
+ error = undefined;
+ try {
+ obj = new Constructor(undefined, options);
+ } catch (e) {
+ error = e;
+ }
+ if (error === undefined) {
+ throw new Test262Error("Invalid option value " + value + " for property " + property + " was not rejected.");
+ } else if (error.name !== "RangeError") {
+ throw new Test262Error("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + ".");
+ }
+ });
+ }
+
+ // test that fallback value or another valid value is used if no options value is provided
+ if (!noReturn) {
+ options = {};
+ addExtraOptions(options, undefined, testOptions);
+ obj = new Constructor(undefined, options);
+ actual = obj.resolvedOptions()[property];
+ if (!(isOptional && actual === undefined)) {
+ if (fallback !== undefined) {
+ if (actual !== fallback) {
+ throw new Test262Error("Option fallback value " + fallback + " for property " + property +
+ " was not used; got " + actual + " instead.");
+ }
+ } else {
+ if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) {
+ throw new Test262Error("Invalid value " + actual + " returned for property " + property + ".");
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * Properties of the RegExp constructor that may be affected by use of regular
+ * expressions, and the default values of these properties. Properties are from
+ * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Properties
+ */
+var regExpProperties = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",
+ "$_", "$*", "$&", "$+", "$`", "$'",
+ "input", "lastMatch", "lastParen", "leftContext", "rightContext"
+];
+
+var regExpPropertiesDefaultValues = (function () {
+ var values = Object.create(null);
+ (/(?:)/).test("");
+ regExpProperties.forEach(function (property) {
+ values[property] = RegExp[property];
+ });
+ return values;
+}());
+
+
+/**
+ * Tests that executing the provided function (which may use regular expressions
+ * in its implementation) does not create or modify unwanted properties on the
+ * RegExp constructor.
+ */
+function testForUnwantedRegExpChanges(testFunc) {
+ (/(?:)/).test("");
+ testFunc();
+ regExpProperties.forEach(function (property) {
+ if (RegExp[property] !== regExpPropertiesDefaultValues[property]) {
+ throw new Test262Error("RegExp has unexpected property " + property + " with value " +
+ RegExp[property] + ".");
+ }
+ });
+}
+
+
+/**
+ * Returns an array of all known calendars.
+ */
+function allCalendars() {
+ // source: CLDR file common/bcp47/number.xml; version CLDR 39.
+ // https://github.com/unicode-org/cldr/blob/master/common/bcp47/calendar.xml
+ return [
+ "buddhist",
+ "chinese",
+ "coptic",
+ "dangi",
+ "ethioaa",
+ "ethiopic",
+ "gregory",
+ "hebrew",
+ "indian",
+ "islamic",
+ "islamic-umalqura",
+ "islamic-tbla",
+ "islamic-civil",
+ "islamic-rgsa",
+ "iso8601",
+ "japanese",
+ "persian",
+ "roc",
+ ];
+}
+
+
+/**
+ * Returns an array of all known collations.
+ */
+function allCollations() {
+ // source: CLDR file common/bcp47/collation.xml; version CLDR 39.
+ // https://github.com/unicode-org/cldr/blob/master/common/bcp47/collation.xml
+ return [
+ "big5han",
+ "compat",
+ "dict",
+ "direct",
+ "ducet",
+ "emoji",
+ "eor",
+ "gb2312",
+ "phonebk",
+ "phonetic",
+ "pinyin",
+ "reformed",
+ "search",
+ "searchjl",
+ "standard",
+ "stroke",
+ "trad",
+ "unihan",
+ "zhuyin",
+ ];
+}
+
+
+/**
+ * Returns an array of all known numbering systems.
+ */
+function allNumberingSystems() {
+ // source: CLDR file common/bcp47/number.xml; version CLDR 40 & new in Unicode 14.0
+ // https://github.com/unicode-org/cldr/blob/master/common/bcp47/number.xml
+ return [
+ "adlm",
+ "ahom",
+ "arab",
+ "arabext",
+ "armn",
+ "armnlow",
+ "bali",
+ "beng",
+ "bhks",
+ "brah",
+ "cakm",
+ "cham",
+ "cyrl",
+ "deva",
+ "diak",
+ "ethi",
+ "finance",
+ "fullwide",
+ "geor",
+ "gong",
+ "gonm",
+ "grek",
+ "greklow",
+ "gujr",
+ "guru",
+ "hanidays",
+ "hanidec",
+ "hans",
+ "hansfin",
+ "hant",
+ "hantfin",
+ "hebr",
+ "hmng",
+ "hmnp",
+ "java",
+ "jpan",
+ "jpanfin",
+ "jpanyear",
+ "kali",
+ "kawi",
+ "khmr",
+ "knda",
+ "lana",
+ "lanatham",
+ "laoo",
+ "latn",
+ "lepc",
+ "limb",
+ "mathbold",
+ "mathdbl",
+ "mathmono",
+ "mathsanb",
+ "mathsans",
+ "mlym",
+ "modi",
+ "mong",
+ "mroo",
+ "mtei",
+ "mymr",
+ "mymrshan",
+ "mymrtlng",
+ "nagm",
+ "native",
+ "newa",
+ "nkoo",
+ "olck",
+ "orya",
+ "osma",
+ "rohg",
+ "roman",
+ "romanlow",
+ "saur",
+ "shrd",
+ "sind",
+ "sinh",
+ "sora",
+ "sund",
+ "takr",
+ "talu",
+ "taml",
+ "tamldec",
+ "tnsa",
+ "telu",
+ "thai",
+ "tirh",
+ "tibt",
+ "traditio",
+ "vaii",
+ "wara",
+ "wcho",
+ ];
+}
+
+
+/**
+ * Tests whether name is a valid BCP 47 numbering system name
+ * and not excluded from use in the ECMAScript Internationalization API.
+ * @param {string} name the name to be tested.
+ * @return {boolean} whether name is a valid BCP 47 numbering system name and
+ * allowed for use in the ECMAScript Internationalization API.
+ */
+
+function isValidNumberingSystem(name) {
+
+ var numberingSystems = allNumberingSystems();
+
+ var excluded = [
+ "finance",
+ "native",
+ "traditio"
+ ];
+
+
+ return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1;
+}
+
+
+/**
+ * Provides the digits of numbering systems with simple digit mappings,
+ * as specified in 11.3.2.
+ */
+
+var numberingSystemDigits = {
+ adlm: "𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙",
+ ahom: "𑜰𑜱𑜲𑜳𑜴𑜵𑜶𑜷𑜸𑜹",
+ arab: "٠١٢٣٤٥٦٧٨٩",
+ arabext: "۰۱۲۳۴۵۶۷۸۹",
+ bali: "\u1B50\u1B51\u1B52\u1B53\u1B54\u1B55\u1B56\u1B57\u1B58\u1B59",
+ beng: "০১২৩৪৫৬৭৮৯",
+ bhks: "𑱐𑱑𑱒𑱓𑱔𑱕𑱖𑱗𑱘𑱙",
+ brah: "𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯",
+ cakm: "𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿",
+ cham: "꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙",
+ deva: "०१२३४५६७८९",
+ diak: "𑥐𑥑𑥒𑥓𑥔𑥕𑥖𑥗𑥘𑥙",
+ fullwide: "0123456789",
+ gong: "𑶠𑶡𑶢𑶣𑶤𑶥𑶦𑶧𑶨𑶩",
+ gonm: "𑵐𑵑𑵒𑵓𑵔𑵕𑵖𑵗𑵘𑵙",
+ gujr: "૦૧૨૩૪૫૬૭૮૯",
+ guru: "੦੧੨੩੪੫੬੭੮੯",
+ hanidec: "〇一二三四五六七八九",
+ hmng: "𖭐𖭑𖭒𖭓𖭔𖭕𖭖𖭗𖭘𖭙",
+ hmnp: "𞅀𞅁𞅂𞅃𞅄𞅅𞅆𞅇𞅈𞅉",
+ java: "꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙",
+ kali: "꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉",
+ kawi: "\u{11F50}\u{11F51}\u{11F52}\u{11F53}\u{11F54}\u{11F55}\u{11F56}\u{11F57}\u{11F58}\u{11F59}",
+ khmr: "០១២៣៤៥៦៧៨៩",
+ knda: "೦೧೨೩೪೫೬೭೮೯",
+ lana: "᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉",
+ lanatham: "᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙",
+ laoo: "໐໑໒໓໔໕໖໗໘໙",
+ latn: "0123456789",
+ lepc: "᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉",
+ limb: "\u1946\u1947\u1948\u1949\u194A\u194B\u194C\u194D\u194E\u194F",
+ nagm: "\u{1E4F0}\u{1E4F1}\u{1E4F2}\u{1E4F3}\u{1E4F4}\u{1E4F5}\u{1E4F6}\u{1E4F7}\u{1E4F8}\u{1E4F9}",
+ mathbold: "𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗",
+ mathdbl: "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡",
+ mathmono: "𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿",
+ mathsanb: "𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵",
+ mathsans: "𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫",
+ mlym: "൦൧൨൩൪൫൬൭൮൯",
+ modi: "𑙐𑙑𑙒𑙓𑙔𑙕𑙖𑙗𑙘𑙙",
+ mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙",
+ mroo: "𖩠𖩡𖩢𖩣𖩤𖩥𖩦𖩧𖩨𖩩",
+ mtei: "꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹",
+ mymr: "၀၁၂၃၄၅၆၇၈၉",
+ mymrshan: "႐႑႒႓႔႕႖႗႘႙",
+ mymrtlng: "꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹",
+ newa: "𑑐𑑑𑑒𑑓𑑔𑑕𑑖𑑗𑑘𑑙",
+ nkoo: "߀߁߂߃߄߅߆߇߈߉",
+ olck: "᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙",
+ orya: "୦୧୨୩୪୫୬୭୮୯",
+ osma: "𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩",
+ rohg: "𐴰𐴱𐴲𐴳𐴴𐴵𐴶𐴷𐴸𐴹",
+ saur: "꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙",
+ segment: "🯰🯱🯲🯳🯴🯵🯶🯷🯸🯹",
+ shrd: "𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙",
+ sind: "𑋰𑋱𑋲𑋳𑋴𑋵𑋶𑋷𑋸𑋹",
+ sinh: "෦෧෨෩෪෫෬෭෮෯",
+ sora: "𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹",
+ sund: "᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹",
+ takr: "𑛀𑛁𑛂𑛃𑛄𑛅𑛆𑛇𑛈𑛉",
+ talu: "᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙",
+ tamldec: "௦௧௨௩௪௫௬௭௮௯",
+ tnsa: "\u{16AC0}\u{16AC1}\u{16AC2}\u{16AC3}\u{16AC4}\u{16AC5}\u{16AC6}\u{16AC7}\u{16AC8}\u{16AC9}",
+ telu: "౦౧౨౩౪౫౬౭౮౯",
+ thai: "๐๑๒๓๔๕๖๗๘๙",
+ tibt: "༠༡༢༣༤༥༦༧༨༩",
+ tirh: "𑓐𑓑𑓒𑓓𑓔𑓕𑓖𑓗𑓘𑓙",
+ vaii: "꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩",
+ wara: "𑣠𑣡𑣢𑣣𑣤𑣥𑣦𑣧𑣨𑣩",
+ wcho: "𞋰𞋱𞋲𞋳𞋴𞋵𞋶𞋷𞋸𞋹",
+};
+
+
+/**
+ * Returns an array of all simple, sanctioned unit identifiers.
+ */
+function allSimpleSanctionedUnits() {
+ // https://tc39.es/ecma402/#table-sanctioned-simple-unit-identifiers
+ return [
+ "acre",
+ "bit",
+ "byte",
+ "celsius",
+ "centimeter",
+ "day",
+ "degree",
+ "fahrenheit",
+ "fluid-ounce",
+ "foot",
+ "gallon",
+ "gigabit",
+ "gigabyte",
+ "gram",
+ "hectare",
+ "hour",
+ "inch",
+ "kilobit",
+ "kilobyte",
+ "kilogram",
+ "kilometer",
+ "liter",
+ "megabit",
+ "megabyte",
+ "meter",
+ "microsecond",
+ "mile",
+ "mile-scandinavian",
+ "milliliter",
+ "millimeter",
+ "millisecond",
+ "minute",
+ "month",
+ "nanosecond",
+ "ounce",
+ "percent",
+ "petabyte",
+ "pound",
+ "second",
+ "stone",
+ "terabit",
+ "terabyte",
+ "week",
+ "yard",
+ "year",
+ ];
+}
+
+
+/**
+ * Tests that number formatting is handled correctly. The function checks that the
+ * digit sequences in formatted output are as specified, converted to the
+ * selected numbering system, and embedded in consistent localized patterns.
+ * @param {Array} locales the locales to be tested.
+ * @param {Array} numberingSystems the numbering systems to be tested.
+ * @param {Object} options the options to pass to Intl.NumberFormat. Options
+ * must include {useGrouping: false}, and must cause 1.1 to be formatted
+ * pre- and post-decimal digits.
+ * @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings
+ * in unlocalized format with Western digits.
+ */
+
+function testNumberFormat(locales, numberingSystems, options, testData) {
+ locales.forEach(function (locale) {
+ numberingSystems.forEach(function (numbering) {
+ var digits = numberingSystemDigits[numbering];
+ var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options);
+
+ function getPatternParts(positive) {
+ var n = positive ? 1.1 : -1.1;
+ var formatted = format.format(n);
+ var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)";
+ var match = formatted.match(new RegExp(oneoneRE));
+ if (match === null) {
+ throw new Test262Error("Unexpected formatted " + n + " for " +
+ format.resolvedOptions().locale + " and options " +
+ JSON.stringify(options) + ": " + formatted);
+ }
+ return match;
+ }
+
+ function toNumbering(raw) {
+ return raw.replace(/[0-9]/g, function (digit) {
+ return digits[digit.charCodeAt(0) - "0".charCodeAt(0)];
+ });
+ }
+
+ function buildExpected(raw, patternParts) {
+ var period = raw.indexOf(".");
+ if (period === -1) {
+ return patternParts[1] + toNumbering(raw) + patternParts[3];
+ } else {
+ return patternParts[1] +
+ toNumbering(raw.substring(0, period)) +
+ patternParts[2] +
+ toNumbering(raw.substring(period + 1)) +
+ patternParts[3];
+ }
+ }
+
+ if (format.resolvedOptions().numberingSystem === numbering) {
+ // figure out prefixes, infixes, suffixes for positive and negative values
+ var posPatternParts = getPatternParts(true);
+ var negPatternParts = getPatternParts(false);
+
+ Object.getOwnPropertyNames(testData).forEach(function (input) {
+ var rawExpected = testData[input];
+ var patternParts;
+ if (rawExpected[0] === "-") {
+ patternParts = negPatternParts;
+ rawExpected = rawExpected.substring(1);
+ } else {
+ patternParts = posPatternParts;
+ }
+ var expected = buildExpected(rawExpected, patternParts);
+ var actual = format.format(input);
+ if (actual !== expected) {
+ throw new Test262Error("Formatted value for " + input + ", " +
+ format.resolvedOptions().locale + " and options " +
+ JSON.stringify(options) + " is " + actual + "; expected " + expected + ".");
+ }
+ });
+ }
+ });
+ });
+}
+
+
+/**
+ * Return the components of date-time formats.
+ * @return {Array} an array with all date-time components.
+ */
+
+function getDateTimeComponents() {
+ return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"];
+}
+
+
+/**
+ * Return the valid values for the given date-time component, as specified
+ * by the table in section 12.1.1.
+ * @param {string} component a date-time component.
+ * @return {Array} an array with the valid values for the component.
+ */
+
+function getDateTimeComponentValues(component) {
+
+ var components = {
+ weekday: ["narrow", "short", "long"],
+ era: ["narrow", "short", "long"],
+ year: ["2-digit", "numeric"],
+ month: ["2-digit", "numeric", "narrow", "short", "long"],
+ day: ["2-digit", "numeric"],
+ hour: ["2-digit", "numeric"],
+ minute: ["2-digit", "numeric"],
+ second: ["2-digit", "numeric"],
+ timeZoneName: ["short", "long"]
+ };
+
+ var result = components[component];
+ if (result === undefined) {
+ throw new Test262Error("Internal error: No values defined for date-time component " + component + ".");
+ }
+ return result;
+}
+
+
+/**
+ * @description Tests whether timeZone is a String value representing a
+ * structurally valid and canonicalized time zone name, as defined in
+ * sections 6.4.1 and 6.4.2 of the ECMAScript Internationalization API
+ * Specification.
+ * @param {String} timeZone the string to be tested.
+ * @result {Boolean} whether the test succeeded.
+ */
+
+function isCanonicalizedStructurallyValidTimeZoneName(timeZone) {
+ /**
+ * Regular expression defining IANA Time Zone names.
+ *
+ * Spec: IANA Time Zone Database, Theory file
+ */
+ var fileNameComponent = "(?:[A-Za-z_]|\\.(?!\\.?(?:/|$)))[A-Za-z.\\-_]{0,13}";
+ var fileName = fileNameComponent + "(?:/" + fileNameComponent + ")*";
+ var etcName = "(?:Etc/)?GMT[+-]\\d{1,2}";
+ var systemVName = "SystemV/[A-Z]{3}\\d{1,2}(?:[A-Z]{3})?";
+ var legacyName = etcName + "|" + systemVName + "|CST6CDT|EST5EDT|MST7MDT|PST8PDT|NZ";
+ var zoneNamePattern = new RegExp("^(?:" + fileName + "|" + legacyName + ")$");
+
+ if (typeof timeZone !== "string") {
+ return false;
+ }
+ // 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3
+ if (timeZone === "UTC") {
+ return true;
+ }
+ // 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3
+ if (timeZone === "Etc/UTC" || timeZone === "Etc/GMT") {
+ return false;
+ }
+ return zoneNamePattern.test(timeZone);
+}
+
+
+/**
+ * @description Simplified PartitionDurationFormatPattern implementation which
+ * only supports the "en" locale.
+ * @param {Object} duration the duration record
+ * @param {String} style the duration format style
+ * @result {Array} an array with formatted duration parts
+ */
+
+function partitionDurationFormatPattern(duration, style = "short") {
+ const units = [
+ "years",
+ "months",
+ "weeks",
+ "days",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ ];
+
+ function durationToFractionalSeconds(duration) {
+ let {
+ seconds = 0,
+ milliseconds = 0,
+ microseconds = 0,
+ nanoseconds = 0,
+ } = duration;
+
+ // Directly return seconds when no sub-seconds are present.
+ if (milliseconds === 0 && microseconds === 0 && nanoseconds === 0) {
+ return seconds;
+ }
+
+ // Otherwise compute the overall amount of nanoseconds using BigInt to avoid
+ // loss of precision.
+ let ns_sec = BigInt(seconds) * 1_000_000_000n;
+ let ns_ms = BigInt(milliseconds) * 1_000_000n;
+ let ns_us = BigInt(microseconds) * 1_000n;
+ let ns = ns_sec + ns_ms + ns_us + BigInt(nanoseconds);
+
+ // Split the nanoseconds amount into seconds and sub-seconds.
+ let q = ns / 1_000_000_000n;
+ let r = ns % 1_000_000_000n;
+
+ // Pad sub-seconds, without any leading negative sign, to nine digits.
+ if (r < 0) {
+ r = -r;
+ }
+ r = String(r).padStart(9, "0");
+
+ // Return seconds with fractional part as a decimal string.
+ return `${q}.${r}`;
+ }
+
+ // Only "en" is supported.
+ const locale = "en";
+ const numberingSystem = "latn";
+ const timeSeparator = ":";
+
+ let result = [];
+ let separated = false;
+
+ for (let unit of units) {
+ // Absent units default to zero.
+ let value = duration[unit] ?? 0;
+
+ let display = "auto";
+ if (style === "digital") {
+ // Always display numeric units per GetDurationUnitOptions.
+ if (unit === "hours" || unit === "minutes" || unit === "seconds") {
+ display = "always";
+ }
+
+ // Numeric seconds and sub-seconds are combined into a single value.
+ if (unit === "seconds") {
+ value = durationToFractionalSeconds(duration);
+ }
+ }
+
+ // "auto" display omits zero units.
+ if (value !== 0 || display !== "auto") {
+ // Map the DurationFormat style to a NumberFormat style.
+ let unitStyle = style;
+ if (style === "digital") {
+ if (unit === "hours") {
+ unitStyle = "numeric";
+ } else if (unit === "minutes" || unit === "seconds") {
+ unitStyle = "2-digit";
+ } else {
+ unitStyle = "short";
+ }
+ }
+
+ // NumberFormat requires singular unit names.
+ let numberFormatUnit = unit.slice(0, -1);
+
+ // Compute the matching NumberFormat options.
+ let nfOpts;
+ if (unitStyle !== "numeric" && unitStyle !== "2-digit") {
+ // The value is formatted as a standalone unit.
+ nfOpts = {
+ numberingSystem,
+ style: "unit",
+ unit: numberFormatUnit,
+ unitDisplay: unitStyle,
+ };
+ } else {
+ let roundingMode = undefined;
+ let minimumFractionDigits = undefined;
+ let maximumFractionDigits = undefined;
+
+ // Numeric seconds include any sub-seconds.
+ if (style === "digital" && unit === "seconds") {
+ roundingMode = "trunc";
+ minimumFractionDigits = 0;
+ maximumFractionDigits = 9;
+ }
+
+ // The value is formatted as a numeric unit.
+ nfOpts = {
+ numberingSystem,
+ minimumIntegerDigits: (unitStyle === "2-digit" ? 2 : 1),
+ roundingMode,
+ minimumFractionDigits,
+ maximumFractionDigits,
+ };
+ }
+
+ let nf = new Intl.NumberFormat(locale, nfOpts);
+ let formatted = nf.formatToParts(value);
+
+ // Add |numberFormatUnit| to the formatted number.
+ let list = [];
+ for (let {value, type} of formatted) {
+ list.push({type, value, unit: numberFormatUnit});
+ }
+
+ if (!separated) {
+ // Prepend the separator before the next numeric unit.
+ if (unitStyle === "2-digit" || unitStyle === "numeric") {
+ separated = true;
+ }
+
+ // Append the formatted number to |result|.
+ result.push(list);
+ } else {
+ let last = result[result.length - 1];
+
+ // Prepend the time separator before the formatted number.
+ last.push({
+ type: "literal",
+ value: timeSeparator,
+ });
+
+ // Concatenate |last| and the formatted number.
+ last.push(...list);
+ }
+ } else {
+ separated = false;
+ }
+
+ // No further units possible after "seconds" when style is "digital".
+ if (style === "digital" && unit === "seconds") {
+ break;
+ }
+ }
+
+ let lf = new Intl.ListFormat(locale, {
+ type: "unit",
+ style: (style !== "digital" ? style : "short"),
+ });
+
+ // Collect all formatted units into a list of strings.
+ let strings = [];
+ for (let parts of result) {
+ let string = "";
+ for (let {value} of parts) {
+ string += value;
+ }
+ strings.push(string);
+ }
+
+ // Format the list of strings and compute the overall result.
+ let flattened = [];
+ for (let {type, value} of lf.formatToParts(strings)) {
+ if (type === "element") {
+ flattened.push(...result.shift());
+ } else {
+ flattened.push({type, value});
+ }
+ }
+ return flattened;
+}
+
+
+/**
+ * @description Return the formatted string from partitionDurationFormatPattern.
+ * @param {Object} duration the duration record
+ * @param {String} style the duration format style
+ * @result {String} a string containing the formatted duration
+ */
+
+function formatDurationFormatPattern(duration, style) {
+ return partitionDurationFormatPattern(duration, style).reduce((acc, e) => acc + e.value, "");
+}
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-consistent-with-resolvedOptions.js b/js/src/tests/test262/intl402/supportedLocalesOf-consistent-with-resolvedOptions.js
new file mode 100644
index 0000000000..2aab6425e1
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-consistent-with-resolvedOptions.js
@@ -0,0 +1,38 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.2
+description: >
+ Tests that locales that are reported by resolvedOptions are also
+ reported by supportedLocalesOf.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ // this test should work equally for both matching algorithms
+ ["lookup", "best fit"].forEach(function (matcher) {
+ var info = getLocaleSupportInfo(Constructor, {localeMatcher: matcher});
+ var supportedByConstructor = info.supported.concat(info.byFallback);
+ var supported = Constructor.supportedLocalesOf(supportedByConstructor,
+ {localeMatcher: matcher});
+ // we could check the length first, but it's probably more interesting which locales are missing
+ var i = 0;
+ var limit = Math.min(supportedByConstructor.length, supported.length);
+ while (i < limit && supportedByConstructor[i] === supported[i]) {
+ i++;
+ }
+ assert.sameValue(i < supportedByConstructor.length, false, "Locale " + supportedByConstructor[i] + " is returned by resolvedOptions but not by supportedLocalesOf.");
+ assert.sameValue(i < supported.length, false, "Locale " + supported[i] + " is returned by supportedLocalesOf but not by resolvedOptions.");
+ });
+
+ // this test is only valid for lookup - best fit may find additional locales supported
+ var info = getLocaleSupportInfo(Constructor, {localeMatcher: "lookup"});
+ var unsupportedByConstructor = info.unsupported;
+ var supported = Constructor.supportedLocalesOf(unsupportedByConstructor,
+ {localeMatcher: "lookup"});
+ assert.sameValue(supported.length > 0, false, "Locale " + supported[0] + " is returned by supportedLocalesOf but not by resolvedOptions.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-default-locale-and-zxx-locale.js b/js/src/tests/test262/intl402/supportedLocalesOf-default-locale-and-zxx-locale.js
new file mode 100644
index 0000000000..456c669f5f
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-default-locale-and-zxx-locale.js
@@ -0,0 +1,26 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.6_4_c
+description: >
+ Tests that LookupSupportedLocales includes the default locale and
+ doesn't include the "no linguistic content" locale.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ // this test should work equally for both matching algorithms
+ ["lookup", "best fit"].forEach(function (matcher) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var noLinguisticContent = "zxx";
+ var supported = Constructor.supportedLocalesOf([defaultLocale, noLinguisticContent],
+ {localeMatcher: matcher});
+ assert.notSameValue(supported.indexOf(defaultLocale), -1, "SupportedLocales didn't return default locale with matcher " + matcher + ".");
+ assert.sameValue(supported.indexOf(noLinguisticContent), -1, "SupportedLocales returned the \"no linguistic content\" locale with matcher " + matcher + ".");
+ assert.sameValue(supported.length > 1, false, "SupportedLocales returned stray locales: " + supported.join(", ") + " with matcher " + matcher + ".");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-duplicate-elements-removed.js b/js/src/tests/test262/intl402/supportedLocalesOf-duplicate-elements-removed.js
new file mode 100644
index 0000000000..6b8d818d13
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-duplicate-elements-removed.js
@@ -0,0 +1,19 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_8_c_vi
+description: >
+ Tests that canonicalization of locale lists removes duplicate
+ language tags.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var canonicalized = Constructor.supportedLocalesOf([defaultLocale, defaultLocale]);
+ assert.sameValue(canonicalized.length > 1, false, "Canonicalization didn't remove duplicate language tags from locale list.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-empty-and-undefined.js b/js/src/tests/test262/intl402/supportedLocalesOf-empty-and-undefined.js
new file mode 100644
index 0000000000..2959f5ba7b
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-empty-and-undefined.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_1
+description: >
+ Tests that canonicalization of locale lists treats undefined and
+ empty lists the same.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var supportedForUndefined = Constructor.supportedLocalesOf(undefined);
+ var supportedForEmptyList = Constructor.supportedLocalesOf([]);
+ assert.sameValue(supportedForUndefined.length, supportedForEmptyList.length, "Supported locales differ between undefined and empty list input.");
+ // we don't compare the elements because length should be 0 - let's just verify that
+ assert.sameValue(supportedForUndefined.length, 0, "Internal test error: Assumption about length being 0 is invalid.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-coered-to-object.js b/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-coered-to-object.js
new file mode 100644
index 0000000000..9abbeeae27
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-coered-to-object.js
@@ -0,0 +1,33 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_4
+description: >
+ Tests that non-objects are converted to objects before
+ canonicalization.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ // undefined is handled separately
+
+ // null should result in a TypeError
+ assert.throws(TypeError, function() {
+ var supportedForNull = Constructor.supportedLocalesOf(null);
+ }, "Null as locale list was not rejected.");
+
+ // let's use an empty list for comparison
+ var supportedForEmptyList = Constructor.supportedLocalesOf([]);
+ // we don't compare the elements because length should be 0 - let's just verify that
+ assert.sameValue(supportedForEmptyList.length, 0, "Internal test error: Assumption about length being 0 is invalid.");
+
+ // most non-objects will be interpreted as empty lists because a missing length property is interpreted as 0
+ var supportedForNumber = Constructor.supportedLocalesOf(5);
+ assert.sameValue(supportedForNumber.length, supportedForEmptyList.length, "Supported locales differ between numeric and empty list input.");
+ var supportedForBoolean = Constructor.supportedLocalesOf(true);
+ assert.sameValue(supportedForBoolean.length, supportedForEmptyList.length, "Supported locales differ between boolean and empty list input.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-empty-array.js b/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-empty-array.js
new file mode 100644
index 0000000000..87009af4c8
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-locales-arg-empty-array.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.6_4
+description: >
+ Tests that LookupSupportedLocales returns an empty list when
+ given an empty list.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ // this test should work equally for both matching algorithms
+ ["lookup", "best fit"].forEach(function (matcher) {
+ var supported = Constructor.supportedLocalesOf([], {localeMatcher: matcher});
+ assert.sameValue(supported.length, 0, "SupportedLocales with matcher " + matcher + " returned a non-empty list for an empty list.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-returned-array-elements-are-not-frozen.js b/js/src/tests/test262/intl402/supportedLocalesOf-returned-array-elements-are-not-frozen.js
new file mode 100644
index 0000000000..3055f0d71f
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-returned-array-elements-are-not-frozen.js
@@ -0,0 +1,35 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.8_4
+description: >
+ Tests that the array returned by SupportedLocales is extensible,
+ writable and configurable.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+function testNormalProperty(obj, property) {
+ var desc = Object.getOwnPropertyDescriptor(obj, property);
+ assert.sameValue(desc.writable, true, "Property " + property + " of object returned by SupportedLocales is not writable.");
+ assert.sameValue(desc.configurable, true, "Property " + property + " of object returned by SupportedLocales is not configurable.");
+}
+
+function testLengthProperty(obj, property) {
+ var desc = Object.getOwnPropertyDescriptor(obj, property);
+ assert.sameValue(desc.writable, true, "Property " + property + " of object returned by SupportedLocales is not writable.");
+ assert.sameValue(desc.configurable, false, "Property " + property + " of object returned by SupportedLocales is configurable.");
+}
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var supported = Constructor.supportedLocalesOf([defaultLocale]);
+ assert(Object.isExtensible(supported), "Object returned by SupportedLocales is not extensible.");
+ for (var i = 0; i < supported.length; i++) {
+ testNormalProperty(supported, i);
+ }
+ testLengthProperty(supported, "length");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array-2.js b/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array-2.js
new file mode 100644
index 0000000000..d21cf3d5e6
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array-2.js
@@ -0,0 +1,25 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.6_2
+description: >
+ Tests that the behavior of a List is not affected by adversarial
+ changes to Array.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintArray();
+
+testWithIntlConstructors(function (Constructor) {
+ // this test should work equally for both matching algorithms
+ ["lookup", "best fit"].forEach(function (matcher) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var canonicalized = Constructor.supportedLocalesOf([defaultLocale, defaultLocale],
+ {localeMatcher: matcher});
+ assert.sameValue(canonicalized.length > 1, false, "Canonicalization with matcher " + matcher + " didn't remove duplicate language tags from locale list.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array.js b/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array.js
new file mode 100644
index 0000000000..ca9db65933
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-taint-Array.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_2
+description: >
+ Tests that the behavior of a List is not affected by adversarial
+ changes to Array.prototype.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+taintArray();
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+ var canonicalized = Constructor.supportedLocalesOf([defaultLocale, defaultLocale]);
+ assert.sameValue(canonicalized.length > 1, false, "Canonicalization didn't remove duplicate language tags from locale list.");
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-test-option-localeMatcher.js b/js/src/tests/test262/intl402/supportedLocalesOf-test-option-localeMatcher.js
new file mode 100644
index 0000000000..a29cb11742
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-test-option-localeMatcher.js
@@ -0,0 +1,27 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.8_1_c
+description: Tests that the option localeMatcher is processed correctly.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+ var defaultLocale = new Constructor().resolvedOptions().locale;
+
+ var validValues = [undefined, "lookup", "best fit", {toString: function () { return "lookup"; }}];
+ validValues.forEach(function (value) {
+ var supported = Constructor.supportedLocalesOf([defaultLocale], {localeMatcher: value});
+ });
+
+ var invalidValues = [null, 0, 5, NaN, true, false, "invalid"];
+ invalidValues.forEach(function (value) {
+ assert.throws(RangeError, function() {
+ var supported = Constructor.supportedLocalesOf([defaultLocale], {localeMatcher: value});
+ }, "Invalid localeMatcher value " + value + " was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-throws-if-element-not-string-or-object.js b/js/src/tests/test262/intl402/supportedLocalesOf-throws-if-element-not-string-or-object.js
new file mode 100644
index 0000000000..efcdd7d04c
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-throws-if-element-not-string-or-object.js
@@ -0,0 +1,21 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.1_8_c_ii
+description: Tests that values other than strings are not accepted as locales.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+var notStringOrObject = [undefined, null, true, false, 0, 5, -5, NaN];
+
+testWithIntlConstructors(function (Constructor) {
+ notStringOrObject.forEach(function (value) {
+ assert.throws(TypeError, function() {
+ var supported = Constructor.supportedLocalesOf([value]);
+ }, "" + value + " as locale was not rejected.");
+ });
+});
+
+reportCompare(0, 0);
diff --git a/js/src/tests/test262/intl402/supportedLocalesOf-unicode-extensions-ignored.js b/js/src/tests/test262/intl402/supportedLocalesOf-unicode-extensions-ignored.js
new file mode 100644
index 0000000000..e390d5db0d
--- /dev/null
+++ b/js/src/tests/test262/intl402/supportedLocalesOf-unicode-extensions-ignored.js
@@ -0,0 +1,39 @@
+// Copyright 2012 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+es5id: 9.2.6_4_b
+description: >
+ Tests that Unicode locale extension sequences do not affect
+ whether a locale is considered supported, but are reported back.
+author: Norbert Lindenberg
+includes: [testIntl.js]
+---*/
+
+testWithIntlConstructors(function (Constructor) {
+
+ // this test should work equally for both matching algorithms
+ ["lookup", "best fit"].forEach(function (matcher) {
+ var opt = {localeMatcher: matcher};
+ var info = getLocaleSupportInfo(Constructor, opt);
+ var allLocales = info.supported.concat(info.byFallback, info.unsupported);
+ allLocales.forEach(function (locale) {
+ var validExtension = "-u-co-phonebk-nu-latn";
+ var invalidExtension = "-u-nu-invalid";
+ var supported1 = Constructor.supportedLocalesOf([locale], opt);
+ var supported2 = Constructor.supportedLocalesOf([locale + validExtension], opt);
+ var supported3 = Constructor.supportedLocalesOf([locale + invalidExtension], opt);
+ if (supported1.length === 1) {
+ assert.sameValue(supported2.length, 1, "#1.1: Presence of Unicode locale extension sequence affects whether locale " + locale + " is considered supported with matcher " + matcher + ".");
+ assert.sameValue(supported3.length, 1, "#1.2: Presence of Unicode locale extension sequence affects whether locale " + locale + " is considered supported with matcher " + matcher + ".");
+ assert.sameValue(supported2[0], locale + validExtension, "#2.1: Unicode locale extension sequence is not correctly returned for locale " + locale + " with matcher " + matcher + ".");
+ assert.sameValue(supported3[0], locale + invalidExtension, "#2.2: Unicode locale extension sequence is not correctly returned for locale " + locale + " with matcher " + matcher + ".");
+ } else {
+ assert.sameValue(supported2.length, 0, "#3.1: Presence of Unicode locale extension sequence affects whether locale " + locale + " is considered supported with matcher " + matcher + ".");
+ assert.sameValue(supported3.length, 0, "#3.2: Presence of Unicode locale extension sequence affects whether locale " + locale + " is considered supported with matcher " + matcher + ".");
+ }
+ });
+ });
+});
+
+reportCompare(0, 0);